v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
gdb-server.cc
Go to the documentation of this file.
1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <inttypes.h>
8#include <functional>
9#include "src/api/api-inl.h"
10#include "src/api/api.h"
11#include "src/debug/debug.h"
14
15namespace v8 {
16namespace internal {
17namespace wasm {
18namespace gdb_server {
19
20static const uint32_t kMaxWasmCallStack = 20;
21
22// A TaskRunner is an object that runs posted tasks (in the form of closure
23// objects). Tasks are queued and run, in order, in the thread where the
24// TaskRunner::RunMessageLoop() is called.
26 public:
27 // Class Task wraps a std::function with a semaphore to signal its completion.
28 // This logic would be neatly implemented with std::packaged_tasks but we
29 // cannot use <future> in V8.
30 class Task {
31 public:
32 Task(base::Semaphore* ready_semaphore, std::function<void()> func)
33 : ready_semaphore_(ready_semaphore), func_(func) {}
34
35 void Run() {
36 func_();
38 }
39
40 // A semaphore object passed by the thread that posts a task.
41 // The sender can Wait on this semaphore to block until the task has
42 // completed execution in the TaskRunner thread.
44
45 // The function to run.
46 std::function<void()> func_;
47 };
48
53
54 TaskRunner(const TaskRunner&) = delete;
55 TaskRunner& operator=(const TaskRunner&) = delete;
56
57 // Starts the task runner. All tasks posted are run, in order, in the thread
58 // that calls this function.
59 void Run() {
60 is_terminated_ = false;
61 int loop_number = ++nested_loop_count_;
62 while (nested_loop_count_ == loop_number && !is_terminated_) {
63 std::shared_ptr<Task> task = GetNext();
64 if (task) {
65 task->Run();
66 }
67 }
68 }
69
70 // Terminates the task runner. Tasks that are still pending in the queue are
71 // not discarded and will be executed when the task runner is restarted.
79
80 // Posts a task to the task runner, to be executed in the task runner thread.
81 template <typename Functor>
82 auto Append(base::Semaphore* ready_semaphore, Functor&& task) {
83 queue_.Enqueue(std::make_shared<Task>(ready_semaphore, task));
85 }
86
87 private:
88 std::shared_ptr<Task> GetNext() {
89 while (!is_terminated_) {
90 if (queue_.IsEmpty()) {
92 }
93
94 std::shared_ptr<Task> task;
95 if (queue_.Dequeue(&task)) {
96 return task;
97 }
98 }
99 return nullptr;
100 }
101
105 std::atomic<bool> is_terminated_;
106};
107
108GdbServer::GdbServer() : has_module_list_changed_(false) {
109 task_runner_ = std::make_unique<TaskRunner>();
110}
111
112template <typename Functor>
113auto GdbServer::RunSyncTask(Functor&& callback) const {
114 // Executed in the GDBServerThread.
115 v8::base::Semaphore ready_semaphore(0);
116 task_runner_->Append(&ready_semaphore, callback);
117 ready_semaphore.Wait();
118}
119
120// static
121std::unique_ptr<GdbServer> GdbServer::Create() {
122 DCHECK(v8_flags.wasm_gdb_remote);
123
124 std::unique_ptr<GdbServer> gdb_server(new GdbServer());
125
126 // Spawns the GDB-stub thread where all the communication with the debugger
127 // happens.
128 gdb_server->thread_ = std::make_unique<GdbServerThread>(gdb_server.get());
129 if (!gdb_server->thread_->StartAndInitialize()) {
131 "Cannot initialize thread, GDB-remote debugging will be disabled.\n");
132 return nullptr;
133 }
134 return gdb_server;
135}
136
138 // All Isolates have been deregistered.
139 DCHECK(isolate_delegates_.empty());
140
141 if (thread_) {
142 // Waits for the GDB-stub thread to terminate.
143 thread_->Stop();
144 thread_->Join();
145 }
146}
147
149
151
152std::vector<GdbServer::WasmModuleInfo> GdbServer::GetLoadedModules(
153 bool clear_module_list_changed_flag) {
154 // Executed in the GDBServerThread.
155 std::vector<GdbServer::WasmModuleInfo> modules;
156
157 RunSyncTask([this, &modules, clear_module_list_changed_flag]() {
158 // Executed in the isolate thread.
159 for (const auto& pair : scripts_) {
160 uint32_t module_id = pair.first;
161 const WasmModuleDebug& module_debug = pair.second;
162 modules.push_back({module_id, module_debug.GetModuleName()});
163 }
164
165 if (clear_module_list_changed_flag) has_module_list_changed_ = false;
166 });
167 return modules;
168}
169
170bool GdbServer::GetModuleDebugHandler(uint32_t module_id,
171 WasmModuleDebug** wasm_module_debug) {
172 // Always executed in the isolate thread.
173 ScriptsMap::iterator scriptIterator = scripts_.find(module_id);
174 if (scriptIterator != scripts_.end()) {
175 *wasm_module_debug = &scriptIterator->second;
176 return true;
177 }
178 wasm_module_debug = nullptr;
179 return false;
180}
181
182bool GdbServer::GetWasmGlobal(uint32_t frame_index, uint32_t index,
183 uint8_t* buffer, uint32_t buffer_size,
184 uint32_t* size) {
185 // Executed in the GDBServerThread.
186 bool result = false;
187 RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
188 // Executed in the isolate thread.
189 result = WasmModuleDebug::GetWasmGlobal(GetTarget().GetCurrentIsolate(),
190 frame_index, index, buffer,
191 buffer_size, size);
192 });
193 return result;
194}
195
196bool GdbServer::GetWasmLocal(uint32_t frame_index, uint32_t index,
197 uint8_t* buffer, uint32_t buffer_size,
198 uint32_t* size) {
199 // Executed in the GDBServerThread.
200 bool result = false;
201 RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
202 // Executed in the isolate thread.
203 result = WasmModuleDebug::GetWasmLocal(GetTarget().GetCurrentIsolate(),
204 frame_index, index, buffer,
205 buffer_size, size);
206 });
207 return result;
208}
209
210bool GdbServer::GetWasmStackValue(uint32_t frame_index, uint32_t index,
211 uint8_t* buffer, uint32_t buffer_size,
212 uint32_t* size) {
213 // Executed in the GDBServerThread.
214 bool result = false;
215 RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
216 // Executed in the isolate thread.
218 frame_index, index, buffer,
219 buffer_size, size);
220 });
221 return result;
222}
223
224uint32_t GdbServer::GetWasmMemory(uint32_t module_id, uint32_t offset,
225 uint8_t* buffer, uint32_t size) {
226 // Executed in the GDBServerThread.
227 uint32_t bytes_read = 0;
228 RunSyncTask([this, &bytes_read, module_id, offset, buffer, size]() {
229 // Executed in the isolate thread.
230 WasmModuleDebug* module_debug = nullptr;
231 if (GetModuleDebugHandler(module_id, &module_debug)) {
232 bytes_read = module_debug->GetWasmMemory(GetTarget().GetCurrentIsolate(),
233 offset, buffer, size);
234 }
235 });
236 return bytes_read;
237}
238
239uint32_t GdbServer::GetWasmData(uint32_t module_id, uint32_t offset,
240 uint8_t* buffer, uint32_t size) {
241 // Executed in the GDBServerThread.
242 uint32_t bytes_read = 0;
243 RunSyncTask([this, &bytes_read, module_id, offset, buffer, size]() {
244 // Executed in the isolate thread.
245 WasmModuleDebug* module_debug = nullptr;
246 if (GetModuleDebugHandler(module_id, &module_debug)) {
247 bytes_read = module_debug->GetWasmData(GetTarget().GetCurrentIsolate(),
248 offset, buffer, size);
249 }
250 });
251 return bytes_read;
252}
253
254uint32_t GdbServer::GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer,
255 uint32_t size) {
256 // Executed in the GDBServerThread.
257 uint32_t bytes_read = 0;
258 RunSyncTask([this, &bytes_read, wasm_addr, buffer, size]() {
259 // Executed in the isolate thread.
260 WasmModuleDebug* module_debug;
261 if (GetModuleDebugHandler(wasm_addr.ModuleId(), &module_debug)) {
262 bytes_read = module_debug->GetWasmModuleBytes(wasm_addr, buffer, size);
263 }
264 });
265 return bytes_read;
266}
267
268bool GdbServer::AddBreakpoint(uint32_t wasm_module_id, uint32_t offset) {
269 // Executed in the GDBServerThread.
270 bool result = false;
271 RunSyncTask([this, &result, wasm_module_id, offset]() {
272 // Executed in the isolate thread.
273 WasmModuleDebug* module_debug;
274 if (GetModuleDebugHandler(wasm_module_id, &module_debug)) {
275 int breakpoint_id = 0;
276 if (module_debug->AddBreakpoint(offset, &breakpoint_id)) {
277 breakpoints_[wasm_addr_t(wasm_module_id, offset)] = breakpoint_id;
278 result = true;
279 }
280 }
281 });
282 return result;
283}
284
285bool GdbServer::RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset) {
286 // Executed in the GDBServerThread.
287 bool result = false;
288 RunSyncTask([this, &result, wasm_module_id, offset]() {
289 // Executed in the isolate thread.
290 BreakpointsMap::iterator it =
291 breakpoints_.find(wasm_addr_t(wasm_module_id, offset));
292 if (it != breakpoints_.end()) {
293 int breakpoint_id = it->second;
294 breakpoints_.erase(it);
295
296 WasmModuleDebug* module_debug;
297 if (GetModuleDebugHandler(wasm_module_id, &module_debug)) {
298 module_debug->RemoveBreakpoint(offset, breakpoint_id);
299 result = true;
300 }
301 }
302 });
303 return result;
304}
305
306std::vector<wasm_addr_t> GdbServer::GetWasmCallStack() const {
307 // Executed in the GDBServerThread.
308 std::vector<wasm_addr_t> result;
309 RunSyncTask([this, &result]() {
310 // Executed in the isolate thread.
312 });
313 return result;
314}
315
317 // Executed in the isolate thread.
318 if (isolate_delegates_.find(isolate) == isolate_delegates_.end()) {
320 std::make_unique<DebugDelegate>(isolate, this);
321 }
322}
323
325 // Executed in the isolate thread.
326 auto it = isolate_delegates_.find(isolate);
327 if (it != isolate_delegates_.end()) {
328 for (auto it = scripts_.begin(); it != scripts_.end();) {
329 if (it->second.GetIsolate() == isolate) {
330 it = scripts_.erase(it);
332 } else {
333 ++it;
334 }
335 }
336 isolate_delegates_.erase(it);
337 }
338}
339
341 // Executed in the GDBServerThread.
342 auto it = isolate_delegates_.begin();
343 if (it != isolate_delegates_.end()) {
344 Isolate* isolate = it->first;
345 v8::Isolate* v8Isolate = (v8::Isolate*)isolate;
346 v8Isolate->RequestInterrupt(
347 // Executed in the isolate thread.
348 [](v8::Isolate* isolate, void*) {
349 if (v8::debug::AllFramesOnStackAreBlackboxed(isolate)) {
351 } else {
353 }
354 },
355 this);
356 }
357}
358
360 // Executed in the GDBServerThread.
362 RunSyncTask([this, pc]() {
363 // Executed in the isolate thread.
364 WasmModuleDebug* module_debug;
365 if (GetModuleDebugHandler(pc.ModuleId(), &module_debug)) {
366 module_debug->PrepareStep();
367 }
368 });
369}
370
371void GdbServer::AddWasmModule(uint32_t module_id,
372 Local<debug::WasmScript> wasm_script) {
373 // Executed in the isolate thread.
374 DCHECK_EQ(Script::Type::kWasm, Utils::OpenHandle(*wasm_script)->type());
375 v8::Isolate* isolate = wasm_script->GetIsolate();
376 scripts_.insert(
377 std::make_pair(module_id, WasmModuleDebug(isolate, wasm_script)));
379
380 if (v8_flags.wasm_pause_waiting_for_debugger && scripts_.size() == 1) {
381 TRACE_GDB_REMOTE("Paused, waiting for a debugger to attach...\n");
382 Suspend();
383 }
384}
385
386Target& GdbServer::GetTarget() const { return thread_->GetTarget(); }
387
388// static
389std::atomic<uint32_t> GdbServer::DebugDelegate::id_s;
390
392 : isolate_(isolate), id_(id_s++), gdb_server_(gdb_server) {
395
396 // Register the delegate
397 isolate_->debug()->SetDebugDelegate(this);
398 v8::debug::EnterDebuggingForIsolate((v8::Isolate*)isolate_);
401}
402
404 // Deregister the delegate
405 isolate_->debug()->SetDebugDelegate(nullptr);
406}
407
409 bool is_live_edited,
410 bool has_compile_error) {
411 // Executed in the isolate thread.
412 if (script->IsWasm()) {
413 DCHECK_EQ(reinterpret_cast<v8::Isolate*>(isolate_), script->GetIsolate());
414 gdb_server_->AddWasmModule(GetModuleId(script->Id()),
415 script.As<debug::WasmScript>());
416 }
417}
418
420 // Executed in the isolate thread.
421 Local<v8::Context> paused_context,
422 const std::vector<debug::BreakpointId>& inspector_break_points_hit,
423 v8::debug::BreakReasons break_reasons) {
424 gdb_server_->GetTarget().OnProgramBreak(
426 gdb_server_->RunMessageLoopOnPause();
427}
428
430 // Executed in the isolate thread.
431 Local<v8::Context> paused_context, Local<Value> exception,
432 Local<Value> promise, bool is_uncaught,
433 debug::ExceptionType exception_type) {
434 if (exception_type == v8::debug::kException && is_uncaught) {
435 gdb_server_->GetTarget().OnException(
437 gdb_server_->RunMessageLoopOnPause();
438 }
439}
440
442 // Executed in the isolate thread.
444 const debug::Location& end) {
445 return false;
446}
447
448} // namespace gdb_server
449} // namespace wasm
450} // namespace internal
451} // namespace v8
Isolate * isolate_
void RequestInterrupt(InterruptCallback callback, void *data)
Definition api.cc:9904
static v8::internal::Handle< To > OpenHandle(v8::Local< From > handle)
Definition api.h:274
void SetCaptureStackTraceForUncaughtExceptions(bool capture, int frame_limit, StackTrace::StackTraceOptions options)
Definition isolate.cc:3612
Debug * debug() const
Definition isolate.h:1474
DebugDelegate(Isolate *isolate, GdbServer *gdb_server)
void ScriptCompiled(Local< debug::Script > script, bool is_live_edited, bool has_compile_error) override
bool IsFunctionBlackboxed(Local< debug::Script > script, const debug::Location &start, const debug::Location &end) override
void ExceptionThrown(Local< v8::Context > paused_context, Local< Value > exception, Local< Value > promise, bool is_uncaught, debug::ExceptionType exception_type) override
void BreakProgramRequested(Local< v8::Context > paused_context, const std::vector< debug::BreakpointId > &inspector_break_points_hit, v8::debug::BreakReasons break_reasons) override
IsolateDebugDelegateMap isolate_delegates_
Definition gdb-server.h:205
bool GetWasmLocal(uint32_t frame_index, uint32_t index, uint8_t *buffer, uint32_t buffer_size, uint32_t *size)
std::unique_ptr< TaskRunner > task_runner_
Definition gdb-server.h:189
bool AddBreakpoint(uint32_t wasm_module_id, uint32_t offset)
std::unique_ptr< GdbServerThread > thread_
Definition gdb-server.h:185
uint32_t GetWasmModuleBytes(wasm_addr_t address, uint8_t *buffer, uint32_t size)
uint32_t GetWasmData(uint32_t module_id, uint32_t offset, uint8_t *buffer, uint32_t size)
bool GetModuleDebugHandler(uint32_t module_id, WasmModuleDebug **wasm_module_debug)
std::vector< WasmModuleInfo > GetLoadedModules(bool clear_module_list_changed_flag=false)
bool GetWasmStackValue(uint32_t frame_index, uint32_t index, uint8_t *buffer, uint32_t buffer_size, uint32_t *size)
std::vector< wasm_addr_t > GetWasmCallStack() const
auto RunSyncTask(Callback &&callback) const
uint32_t GetWasmMemory(uint32_t module_id, uint32_t offset, uint8_t *buffer, uint32_t size)
bool GetWasmGlobal(uint32_t frame_index, uint32_t index, uint8_t *buffer, uint32_t buffer_size, uint32_t *size)
bool RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset)
static std::unique_ptr< GdbServer > Create()
void AddWasmModule(uint32_t module_id, Local< debug::WasmScript > wasm_script)
const std::vector< wasm_addr_t > GetCallStack() const
Definition target.cc:685
Task(base::Semaphore *ready_semaphore, std::function< void()> func)
Definition gdb-server.cc:32
LockedQueue< std::shared_ptr< Task > > queue_
TaskRunner & operator=(const TaskRunner &)=delete
auto Append(base::Semaphore *ready_semaphore, Functor &&task)
Definition gdb-server.cc:82
TaskRunner(const TaskRunner &)=delete
static std::vector< wasm_addr_t > GetCallStack(uint32_t debug_context_id, Isolate *isolate)
static bool GetWasmStackValue(Isolate *isolate, uint32_t frame_index, uint32_t index, uint8_t *buffer, uint32_t buffer_size, uint32_t *size)
void RemoveBreakpoint(uint32_t offset, int breakpoint_id)
static bool GetWasmLocal(Isolate *isolate, uint32_t frame_index, uint32_t index, uint8_t *buffer, uint32_t buffer_size, uint32_t *size)
uint32_t GetWasmMemory(Isolate *isolate, uint32_t offset, uint8_t *buffer, uint32_t size)
uint32_t GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t *buffer, uint32_t size)
uint32_t GetWasmData(Isolate *isolate, uint32_t offset, uint8_t *buffer, uint32_t size)
static bool GetWasmGlobal(Isolate *isolate, uint32_t frame_index, uint32_t index, uint8_t *buffer, uint32_t buffer_size, uint32_t *size)
bool AddBreakpoint(uint32_t offset, int *breakpoint_id)
int start
int end
#define TRACE_GDB_REMOTE(...)
Isolate * isolate
int32_t offset
TNode< Object > callback
ZoneVector< RpoNumber > & result
void BreakRightNow(Isolate *v8_isolate, base::EnumSet< debug::BreakReason > break_reasons)
void SetBreakOnNextFunctionCall(Isolate *isolate)
void ChangeBreakOnException(Isolate *isolate, ExceptionBreakState type)
static const uint32_t kMaxWasmCallStack
Definition gdb-server.cc:20
typedef void(VECTORCALL PWasmOp)(const uint8_t *code
V8_EXPORT_PRIVATE FlagValues v8_flags
Definition c-api.cc:87
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
#define DCHECK_EQ(v1, v2)
Definition logging.h:485