1 |
|
|
#include "inspector_io.h" |
2 |
|
|
|
3 |
|
|
#include "inspector_socket_server.h" |
4 |
|
|
#include "inspector/main_thread_interface.h" |
5 |
|
|
#include "inspector/node_string.h" |
6 |
|
|
#include "crypto/crypto_util.h" |
7 |
|
|
#include "base_object-inl.h" |
8 |
|
|
#include "debug_utils-inl.h" |
9 |
|
|
#include "node.h" |
10 |
|
|
#include "node_internals.h" |
11 |
|
|
#include "node_mutex.h" |
12 |
|
|
#include "v8-inspector.h" |
13 |
|
|
#include "util-inl.h" |
14 |
|
|
#include "zlib.h" |
15 |
|
|
|
16 |
|
|
#include <deque> |
17 |
|
|
#include <cstring> |
18 |
|
|
#include <vector> |
19 |
|
|
|
20 |
|
|
namespace node { |
21 |
|
|
namespace inspector { |
22 |
|
|
namespace { |
23 |
|
|
using v8_inspector::StringBuffer; |
24 |
|
|
using v8_inspector::StringView; |
25 |
|
|
|
26 |
|
|
// kKill closes connections and stops the server, kStop only stops the server |
27 |
|
|
enum class TransportAction { kKill, kSendMessage, kStop }; |
28 |
|
|
|
29 |
|
115 |
std::string ScriptPath(uv_loop_t* loop, const std::string& script_name) { |
30 |
|
115 |
std::string script_path; |
31 |
|
|
|
32 |
✓✓ |
115 |
if (!script_name.empty()) { |
33 |
|
|
uv_fs_t req; |
34 |
|
98 |
req.ptr = nullptr; |
35 |
✓✓ |
98 |
if (0 == uv_fs_realpath(loop, &req, script_name.c_str(), nullptr)) { |
36 |
✗✓ |
97 |
CHECK_NOT_NULL(req.ptr); |
37 |
|
97 |
script_path = std::string(static_cast<char*>(req.ptr)); |
38 |
|
|
} |
39 |
|
98 |
uv_fs_req_cleanup(&req); |
40 |
|
|
} |
41 |
|
|
|
42 |
|
115 |
return script_path; |
43 |
|
|
} |
44 |
|
|
|
45 |
|
|
// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt |
46 |
|
|
// Used ver 4 - with numbers |
47 |
|
115 |
std::string GenerateID() { |
48 |
|
|
uint16_t buffer[8]; |
49 |
✗✓ |
115 |
CHECK(crypto::CSPRNG(buffer, sizeof(buffer)).is_ok()); |
50 |
|
|
|
51 |
|
|
char uuid[256]; |
52 |
|
920 |
snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x", |
53 |
|
115 |
buffer[0], // time_low |
54 |
|
115 |
buffer[1], // time_mid |
55 |
|
115 |
buffer[2], // time_low |
56 |
|
115 |
(buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version |
57 |
|
115 |
(buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low |
58 |
|
115 |
buffer[5], // node |
59 |
|
115 |
buffer[6], |
60 |
|
115 |
buffer[7]); |
61 |
|
115 |
return uuid; |
62 |
|
|
} |
63 |
|
|
|
64 |
|
|
class RequestToServer { |
65 |
|
|
public: |
66 |
|
2403 |
RequestToServer(TransportAction action, |
67 |
|
|
int session_id, |
68 |
|
|
std::unique_ptr<v8_inspector::StringBuffer> message) |
69 |
|
2403 |
: action_(action), |
70 |
|
|
session_id_(session_id), |
71 |
|
2403 |
message_(std::move(message)) {} |
72 |
|
|
|
73 |
|
2396 |
void Dispatch(InspectorSocketServer* server) const { |
74 |
✓✓✓✗
|
2396 |
switch (action_) { |
75 |
|
42 |
case TransportAction::kKill: |
76 |
|
42 |
server->TerminateConnections(); |
77 |
|
|
// Fallthrough |
78 |
|
150 |
case TransportAction::kStop: |
79 |
|
150 |
server->Stop(); |
80 |
|
150 |
break; |
81 |
|
2246 |
case TransportAction::kSendMessage: |
82 |
|
2246 |
server->Send( |
83 |
|
2246 |
session_id_, |
84 |
|
4492 |
protocol::StringUtil::StringViewToUtf8(message_->string())); |
85 |
|
2246 |
break; |
86 |
|
|
} |
87 |
|
2396 |
} |
88 |
|
|
|
89 |
|
|
private: |
90 |
|
|
TransportAction action_; |
91 |
|
|
int session_id_; |
92 |
|
|
std::unique_ptr<v8_inspector::StringBuffer> message_; |
93 |
|
|
}; |
94 |
|
|
|
95 |
|
|
class RequestQueueData { |
96 |
|
|
public: |
97 |
|
|
using MessageQueue = std::deque<RequestToServer>; |
98 |
|
|
|
99 |
|
115 |
explicit RequestQueueData(uv_loop_t* loop) |
100 |
|
115 |
: handle_(std::make_shared<RequestQueue>(this)) { |
101 |
|
115 |
int err = uv_async_init(loop, &async_, [](uv_async_t* async) { |
102 |
|
|
RequestQueueData* wrapper = |
103 |
|
2336 |
node::ContainerOf(&RequestQueueData::async_, async); |
104 |
|
2336 |
wrapper->DoDispatch(); |
105 |
|
2451 |
}); |
106 |
✗✓ |
115 |
CHECK_EQ(0, err); |
107 |
|
115 |
} |
108 |
|
|
|
109 |
|
|
static void CloseAndFree(RequestQueueData* queue); |
110 |
|
|
|
111 |
|
2403 |
void Post(int session_id, |
112 |
|
|
TransportAction action, |
113 |
|
|
std::unique_ptr<StringBuffer> message) { |
114 |
|
4806 |
Mutex::ScopedLock scoped_lock(state_lock_); |
115 |
|
2403 |
bool notify = messages_.empty(); |
116 |
|
2403 |
messages_.emplace_back(action, session_id, std::move(message)); |
117 |
✓✓ |
2403 |
if (notify) { |
118 |
✗✓ |
2343 |
CHECK_EQ(0, uv_async_send(&async_)); |
119 |
|
2343 |
incoming_message_cond_.Broadcast(scoped_lock); |
120 |
|
|
} |
121 |
|
2403 |
} |
122 |
|
|
|
123 |
|
|
void Wait() { |
124 |
|
|
Mutex::ScopedLock scoped_lock(state_lock_); |
125 |
|
|
if (messages_.empty()) { |
126 |
|
|
incoming_message_cond_.Wait(scoped_lock); |
127 |
|
|
} |
128 |
|
|
} |
129 |
|
|
|
130 |
|
115 |
void SetServer(InspectorSocketServer* server) { |
131 |
|
115 |
server_ = server; |
132 |
|
115 |
} |
133 |
|
|
|
134 |
|
133 |
std::shared_ptr<RequestQueue> handle() { |
135 |
|
133 |
return handle_; |
136 |
|
|
} |
137 |
|
|
|
138 |
|
|
private: |
139 |
|
115 |
~RequestQueueData() = default; |
140 |
|
|
|
141 |
|
2336 |
MessageQueue GetMessages() { |
142 |
|
4672 |
Mutex::ScopedLock scoped_lock(state_lock_); |
143 |
|
2336 |
MessageQueue messages; |
144 |
|
2336 |
messages_.swap(messages); |
145 |
|
2336 |
return messages; |
146 |
|
|
} |
147 |
|
|
|
148 |
|
2336 |
void DoDispatch() { |
149 |
✗✓ |
2336 |
if (server_ == nullptr) |
150 |
|
|
return; |
151 |
✓✓ |
4732 |
for (const auto& request : GetMessages()) { |
152 |
|
2396 |
request.Dispatch(server_); |
153 |
|
|
} |
154 |
|
|
} |
155 |
|
|
|
156 |
|
|
std::shared_ptr<RequestQueue> handle_; |
157 |
|
|
uv_async_t async_; |
158 |
|
|
InspectorSocketServer* server_ = nullptr; |
159 |
|
|
MessageQueue messages_; |
160 |
|
|
Mutex state_lock_; // Locked before mutating the queue. |
161 |
|
|
ConditionVariable incoming_message_cond_; |
162 |
|
|
}; |
163 |
|
|
} // namespace |
164 |
|
|
|
165 |
|
|
class RequestQueue { |
166 |
|
|
public: |
167 |
|
115 |
explicit RequestQueue(RequestQueueData* data) : data_(data) {} |
168 |
|
|
|
169 |
|
115 |
void Reset() { |
170 |
|
115 |
Mutex::ScopedLock scoped_lock(lock_); |
171 |
|
115 |
data_ = nullptr; |
172 |
|
115 |
} |
173 |
|
|
|
174 |
|
2434 |
void Post(int session_id, |
175 |
|
|
TransportAction action, |
176 |
|
|
std::unique_ptr<StringBuffer> message) { |
177 |
|
4868 |
Mutex::ScopedLock scoped_lock(lock_); |
178 |
✓✓ |
2434 |
if (data_ != nullptr) |
179 |
|
2403 |
data_->Post(session_id, action, std::move(message)); |
180 |
|
2434 |
} |
181 |
|
|
|
182 |
|
115 |
bool Expired() { |
183 |
|
115 |
Mutex::ScopedLock scoped_lock(lock_); |
184 |
|
115 |
return data_ == nullptr; |
185 |
|
|
} |
186 |
|
|
|
187 |
|
|
private: |
188 |
|
|
RequestQueueData* data_; |
189 |
|
|
Mutex lock_; |
190 |
|
|
}; |
191 |
|
|
|
192 |
|
|
class IoSessionDelegate : public InspectorSessionDelegate { |
193 |
|
|
public: |
194 |
|
18 |
explicit IoSessionDelegate(std::shared_ptr<RequestQueue> queue, int id) |
195 |
|
18 |
: request_queue_(queue), id_(id) { } |
196 |
|
2246 |
void SendMessageToFrontend(const v8_inspector::StringView& message) override { |
197 |
|
4492 |
request_queue_->Post(id_, TransportAction::kSendMessage, |
198 |
|
4492 |
StringBuffer::create(message)); |
199 |
|
2246 |
} |
200 |
|
|
|
201 |
|
|
private: |
202 |
|
|
std::shared_ptr<RequestQueue> request_queue_; |
203 |
|
|
int id_; |
204 |
|
|
}; |
205 |
|
|
|
206 |
|
|
// Passed to InspectorSocketServer to handle WS inspector protocol events, |
207 |
|
|
// mostly session start, message received, and session end. |
208 |
|
|
class InspectorIoDelegate: public node::inspector::SocketServerDelegate { |
209 |
|
|
public: |
210 |
|
|
InspectorIoDelegate(std::shared_ptr<RequestQueueData> queue, |
211 |
|
|
std::shared_ptr<MainThreadHandle> main_thread, |
212 |
|
|
const std::string& target_id, |
213 |
|
|
const std::string& script_path, |
214 |
|
|
const std::string& script_name); |
215 |
|
460 |
~InspectorIoDelegate() override = default; |
216 |
|
|
|
217 |
|
|
void StartSession(int session_id, const std::string& target_id) override; |
218 |
|
|
void MessageReceived(int session_id, const std::string& message) override; |
219 |
|
|
void EndSession(int session_id) override; |
220 |
|
|
|
221 |
|
|
std::vector<std::string> GetTargetIds() override; |
222 |
|
|
std::string GetTargetTitle(const std::string& id) override; |
223 |
|
|
std::string GetTargetUrl(const std::string& id) override; |
224 |
|
115 |
void AssignServer(InspectorSocketServer* server) override { |
225 |
|
115 |
request_queue_->SetServer(server); |
226 |
|
115 |
} |
227 |
|
|
|
228 |
|
|
private: |
229 |
|
|
std::shared_ptr<RequestQueueData> request_queue_; |
230 |
|
|
std::shared_ptr<MainThreadHandle> main_thread_; |
231 |
|
|
std::unordered_map<int, std::unique_ptr<InspectorSession>> sessions_; |
232 |
|
|
const std::string script_name_; |
233 |
|
|
const std::string script_path_; |
234 |
|
|
const std::string target_id_; |
235 |
|
|
}; |
236 |
|
|
|
237 |
|
|
// static |
238 |
|
115 |
std::unique_ptr<InspectorIo> InspectorIo::Start( |
239 |
|
|
std::shared_ptr<MainThreadHandle> main_thread, |
240 |
|
|
const std::string& path, |
241 |
|
|
std::shared_ptr<ExclusiveAccess<HostPort>> host_port, |
242 |
|
|
const InspectPublishUid& inspect_publish_uid) { |
243 |
|
|
auto io = std::unique_ptr<InspectorIo>( |
244 |
|
|
new InspectorIo(main_thread, |
245 |
|
|
path, |
246 |
|
|
host_port, |
247 |
|
345 |
inspect_publish_uid)); |
248 |
✓✓ |
115 |
if (io->request_queue_->Expired()) { // Thread is not running |
249 |
|
3 |
return nullptr; |
250 |
|
|
} |
251 |
|
112 |
return io; |
252 |
|
|
} |
253 |
|
|
|
254 |
|
115 |
InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> main_thread, |
255 |
|
|
const std::string& path, |
256 |
|
|
std::shared_ptr<ExclusiveAccess<HostPort>> host_port, |
257 |
|
115 |
const InspectPublishUid& inspect_publish_uid) |
258 |
|
|
: main_thread_(main_thread), |
259 |
|
|
host_port_(host_port), |
260 |
|
|
inspect_publish_uid_(inspect_publish_uid), |
261 |
|
|
thread_(), |
262 |
|
|
script_name_(path), |
263 |
|
115 |
id_(GenerateID()) { |
264 |
|
230 |
Mutex::ScopedLock scoped_lock(thread_start_lock_); |
265 |
✗✓ |
115 |
CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0); |
266 |
|
115 |
thread_start_condition_.Wait(scoped_lock); |
267 |
|
115 |
} |
268 |
|
|
|
269 |
|
78 |
InspectorIo::~InspectorIo() { |
270 |
|
78 |
request_queue_->Post(0, TransportAction::kKill, nullptr); |
271 |
|
78 |
int err = uv_thread_join(&thread_); |
272 |
✗✓ |
78 |
CHECK_EQ(err, 0); |
273 |
|
78 |
} |
274 |
|
|
|
275 |
|
110 |
void InspectorIo::StopAcceptingNewConnections() { |
276 |
|
110 |
request_queue_->Post(0, TransportAction::kStop, nullptr); |
277 |
|
110 |
} |
278 |
|
|
|
279 |
|
|
// static |
280 |
|
115 |
void InspectorIo::ThreadMain(void* io) { |
281 |
|
115 |
static_cast<InspectorIo*>(io)->ThreadMain(); |
282 |
|
115 |
} |
283 |
|
|
|
284 |
|
115 |
void InspectorIo::ThreadMain() { |
285 |
|
|
uv_loop_t loop; |
286 |
|
115 |
loop.data = nullptr; |
287 |
|
115 |
int err = uv_loop_init(&loop); |
288 |
✗✓ |
115 |
CHECK_EQ(err, 0); |
289 |
|
115 |
std::shared_ptr<RequestQueueData> queue(new RequestQueueData(&loop), |
290 |
|
230 |
RequestQueueData::CloseAndFree); |
291 |
|
230 |
std::string script_path = ScriptPath(&loop, script_name_); |
292 |
|
|
std::unique_ptr<InspectorIoDelegate> delegate( |
293 |
|
230 |
new InspectorIoDelegate(queue, main_thread_, id_, |
294 |
|
345 |
script_path, script_name_)); |
295 |
|
230 |
std::string host; |
296 |
|
|
int port; |
297 |
|
|
{ |
298 |
|
230 |
ExclusiveAccess<HostPort>::Scoped host_port(host_port_); |
299 |
|
115 |
host = host_port->host(); |
300 |
|
115 |
port = host_port->port(); |
301 |
|
|
} |
302 |
|
115 |
InspectorSocketServer server(std::move(delegate), |
303 |
|
|
&loop, |
304 |
|
115 |
std::move(host), |
305 |
|
|
port, |
306 |
|
345 |
inspect_publish_uid_); |
307 |
|
115 |
request_queue_ = queue->handle(); |
308 |
|
|
// Its lifetime is now that of the server delegate |
309 |
|
115 |
queue.reset(); |
310 |
|
|
{ |
311 |
|
230 |
Mutex::ScopedLock scoped_lock(thread_start_lock_); |
312 |
✓✓ |
115 |
if (server.Start()) { |
313 |
|
224 |
ExclusiveAccess<HostPort>::Scoped host_port(host_port_); |
314 |
|
112 |
host_port->set_port(server.Port()); |
315 |
|
|
} |
316 |
|
115 |
thread_start_condition_.Broadcast(scoped_lock); |
317 |
|
|
} |
318 |
|
115 |
uv_run(&loop, UV_RUN_DEFAULT); |
319 |
|
115 |
CheckedUvLoopClose(&loop); |
320 |
|
115 |
} |
321 |
|
|
|
322 |
|
5 |
std::string InspectorIo::GetWsUrl() const { |
323 |
|
10 |
ExclusiveAccess<HostPort>::Scoped host_port(host_port_); |
324 |
|
5 |
return FormatWsAddress(host_port->host(), host_port->port(), id_, true); |
325 |
|
|
} |
326 |
|
|
|
327 |
|
115 |
InspectorIoDelegate::InspectorIoDelegate( |
328 |
|
|
std::shared_ptr<RequestQueueData> queue, |
329 |
|
|
std::shared_ptr<MainThreadHandle> main_thread, |
330 |
|
|
const std::string& target_id, |
331 |
|
|
const std::string& script_path, |
332 |
|
115 |
const std::string& script_name) |
333 |
|
|
: request_queue_(queue), main_thread_(main_thread), |
334 |
|
|
script_name_(script_name), script_path_(script_path), |
335 |
|
115 |
target_id_(target_id) {} |
336 |
|
|
|
337 |
|
18 |
void InspectorIoDelegate::StartSession(int session_id, |
338 |
|
|
const std::string& target_id) { |
339 |
|
18 |
auto session = main_thread_->Connect( |
340 |
|
36 |
std::unique_ptr<InspectorSessionDelegate>( |
341 |
|
54 |
new IoSessionDelegate(request_queue_->handle(), session_id)), true); |
342 |
✓✗ |
18 |
if (session) { |
343 |
|
18 |
sessions_[session_id] = std::move(session); |
344 |
|
18 |
fprintf(stderr, "Debugger attached.\n"); |
345 |
|
|
} |
346 |
|
18 |
} |
347 |
|
|
|
348 |
|
135 |
void InspectorIoDelegate::MessageReceived(int session_id, |
349 |
|
|
const std::string& message) { |
350 |
|
135 |
auto session = sessions_.find(session_id); |
351 |
✓✗ |
135 |
if (session != sessions_.end()) |
352 |
|
135 |
session->second->Dispatch(Utf8ToStringView(message)->string()); |
353 |
|
135 |
} |
354 |
|
|
|
355 |
|
18 |
void InspectorIoDelegate::EndSession(int session_id) { |
356 |
|
18 |
sessions_.erase(session_id); |
357 |
|
18 |
} |
358 |
|
|
|
359 |
|
154 |
std::vector<std::string> InspectorIoDelegate::GetTargetIds() { |
360 |
✓✓ |
308 |
return { target_id_ }; |
361 |
|
|
} |
362 |
|
|
|
363 |
|
22 |
std::string InspectorIoDelegate::GetTargetTitle(const std::string& id) { |
364 |
✓✓ |
22 |
return script_name_.empty() ? GetHumanReadableProcessName() : script_name_; |
365 |
|
|
} |
366 |
|
|
|
367 |
|
22 |
std::string InspectorIoDelegate::GetTargetUrl(const std::string& id) { |
368 |
|
22 |
return "file://" + script_path_; |
369 |
|
|
} |
370 |
|
|
|
371 |
|
|
// static |
372 |
|
115 |
void RequestQueueData::CloseAndFree(RequestQueueData* queue) { |
373 |
|
115 |
queue->handle_->Reset(); |
374 |
|
115 |
queue->handle_.reset(); |
375 |
|
115 |
uv_close(reinterpret_cast<uv_handle_t*>(&queue->async_), |
376 |
|
115 |
[](uv_handle_t* handle) { |
377 |
|
115 |
uv_async_t* async = reinterpret_cast<uv_async_t*>(handle); |
378 |
|
|
RequestQueueData* wrapper = |
379 |
|
115 |
node::ContainerOf(&RequestQueueData::async_, async); |
380 |
✓✗ |
115 |
delete wrapper; |
381 |
|
115 |
}); |
382 |
|
115 |
} |
383 |
|
|
} // namespace inspector |
384 |
|
|
} // namespace node |