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