Upgrade V8 to 8.8.278.14
Bug: 162604069
Bug: 167389063
Test: gts-tradefed run gts-dev --module GtsGmscoreHostTestCases
--test com.google.android.gts.devicepolicy.DeviceOwnerTest#testProxyPacProxyTest
Test: m -j proxy_resolver_v8_unittest && adb sync && adb shell \
/data/nativetest/proxy_resolver_v8_unittest/proxy_resolver_v8_unittest
Merged-In: Ifb09923b9d7f6d8990fb062d7dc0294edf2c098e
Change-Id: Ifb09923b9d7f6d8990fb062d7dc0294edf2c098e
(cherry picked from commit 9580a23bc5b8874a0979001d3595d027cbb68128)
diff --git a/src/debug/wasm/gdb-server/DIR_METADATA b/src/debug/wasm/gdb-server/DIR_METADATA
new file mode 100644
index 0000000..3b428d9
--- /dev/null
+++ b/src/debug/wasm/gdb-server/DIR_METADATA
@@ -0,0 +1,11 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+monorail {
+ component: "Blink>JavaScript>WebAssembly"
+}
\ No newline at end of file
diff --git a/src/debug/wasm/gdb-server/OWNERS b/src/debug/wasm/gdb-server/OWNERS
new file mode 100644
index 0000000..e2c94e8
--- /dev/null
+++ b/src/debug/wasm/gdb-server/OWNERS
@@ -0,0 +1 @@
+paolosev@microsoft.com
diff --git a/src/debug/wasm/gdb-server/gdb-remote-util.cc b/src/debug/wasm/gdb-server/gdb-remote-util.cc
new file mode 100644
index 0000000..b4880ed
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-remote-util.cc
@@ -0,0 +1,104 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+using std::string;
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+// GDB expects lower case values.
+static const char kHexChars[] = "0123456789abcdef";
+
+void UInt8ToHex(uint8_t byte, char chars[2]) {
+ DCHECK(chars);
+ chars[0] = kHexChars[byte >> 4];
+ chars[1] = kHexChars[byte & 0xF];
+}
+
+bool HexToUInt8(const char chars[2], uint8_t* byte) {
+ uint8_t o1, o2;
+ if (NibbleToUInt8(chars[0], &o1) && NibbleToUInt8(chars[1], &o2)) {
+ *byte = (o1 << 4) + o2;
+ return true;
+ }
+
+ return false;
+}
+
+bool NibbleToUInt8(char ch, uint8_t* byte) {
+ DCHECK(byte);
+
+ // Check for nibble of a-f
+ if ((ch >= 'a') && (ch <= 'f')) {
+ *byte = (ch - 'a' + 10);
+ return true;
+ }
+
+ // Check for nibble of A-F
+ if ((ch >= 'A') && (ch <= 'F')) {
+ *byte = (ch - 'A' + 10);
+ return true;
+ }
+
+ // Check for nibble of 0-9
+ if ((ch >= '0') && (ch <= '9')) {
+ *byte = (ch - '0');
+ return true;
+ }
+
+ // Not a valid nibble representation
+ return false;
+}
+
+std::vector<std::string> StringSplit(const string& instr, const char* delim) {
+ std::vector<std::string> result;
+
+ const char* in = instr.data();
+ if (nullptr == in) return result;
+
+ // Check if we have nothing to do
+ if (nullptr == delim) {
+ result.push_back(string(in));
+ return result;
+ }
+
+ while (*in) {
+ // Toss all preceeding delimiters
+ while (*in && strchr(delim, *in)) in++;
+
+ // If we still have something to process
+ if (*in) {
+ const char* start = in;
+ size_t len = 0;
+ // Keep moving forward for all valid chars
+ while (*in && (strchr(delim, *in) == nullptr)) {
+ len++;
+ in++;
+ }
+
+ // Build this token and add it to the array.
+ result.push_back(string{start, len});
+ }
+ }
+ return result;
+}
+
+std::string Mem2Hex(const uint8_t* mem, size_t count) {
+ std::vector<char> result(count * 2 + 1);
+ for (size_t i = 0; i < count; i++) UInt8ToHex(*mem++, &result[i * 2]);
+ result[count * 2] = '\0';
+ return result.data();
+}
+
+std::string Mem2Hex(const std::string& str) {
+ return Mem2Hex(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-remote-util.h b/src/debug/wasm/gdb-server/gdb-remote-util.h
new file mode 100644
index 0000000..88a2715
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-remote-util.h
@@ -0,0 +1,72 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
+
+#include <string>
+#include <vector>
+#include "src/flags/flags.h"
+#include "src/utils/utils.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+#define TRACE_GDB_REMOTE(...) \
+ do { \
+ if (FLAG_trace_wasm_gdb_remote) PrintF("[gdb-remote] " __VA_ARGS__); \
+ } while (false)
+
+// Convert from 0-255 to a pair of ASCII chars (0-9,a-f).
+void UInt8ToHex(uint8_t byte, char chars[2]);
+
+// Convert a pair of hex chars into a value 0-255 or return false if either
+// input character is not a valid nibble.
+bool HexToUInt8(const char chars[2], uint8_t* byte);
+
+// Convert from ASCII (0-9,a-f,A-F) to 4b unsigned or return false if the
+// input char is unexpected.
+bool NibbleToUInt8(char ch, uint8_t* byte);
+
+std::vector<std::string> V8_EXPORT_PRIVATE StringSplit(const std::string& instr,
+ const char* delim);
+
+// Convert the memory pointed to by {mem} into a hex string in GDB-remote
+// format.
+std::string Mem2Hex(const uint8_t* mem, size_t count);
+std::string Mem2Hex(const std::string& str);
+
+// For LLDB debugging, an address in a Wasm module code space is represented
+// with 64 bits, where the first 32 bits identify the module id:
+// +--------------------+--------------------+
+// | module_id | offset |
+// +--------------------+--------------------+
+// <----- 32 bit -----> <----- 32 bit ----->
+class wasm_addr_t {
+ public:
+ wasm_addr_t(uint32_t module_id, uint32_t offset)
+ : module_id_(module_id), offset_(offset) {}
+ explicit wasm_addr_t(uint64_t address)
+ : module_id_(address >> 32), offset_(address & 0xffffffff) {}
+
+ inline uint32_t ModuleId() const { return module_id_; }
+ inline uint32_t Offset() const { return offset_; }
+
+ inline operator uint64_t() const {
+ return static_cast<uint64_t>(module_id_) << 32 | offset_;
+ }
+
+ private:
+ uint32_t module_id_;
+ uint32_t offset_;
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
diff --git a/src/debug/wasm/gdb-server/gdb-server-thread.cc b/src/debug/wasm/gdb-server/gdb-server-thread.cc
new file mode 100644
index 0000000..03b9b8f
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server-thread.cc
@@ -0,0 +1,118 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
+#include "src/debug/wasm/gdb-server/gdb-server.h"
+#include "src/debug/wasm/gdb-server/session.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+GdbServerThread::GdbServerThread(GdbServer* gdb_server)
+ : Thread(v8::base::Thread::Options("GdbServerThread")),
+ gdb_server_(gdb_server),
+ start_semaphore_(0) {}
+
+bool GdbServerThread::StartAndInitialize() {
+ // Executed in the Isolate thread.
+ if (!Start()) {
+ return false;
+ }
+
+ // We need to make sure that {Stop} is never called before the thread has
+ // completely initialized {transport_} and {target_}. Otherwise there could be
+ // a race condition where in the main thread {Stop} might get called before
+ // the transport is created, and then in the GDBServer thread we may have time
+ // to setup the transport and block on accept() before the main thread blocks
+ // on joining the thread.
+ // The small performance hit caused by this Wait should be negligeable because
+ // this operation happensat most once per process and only when the
+ // --wasm-gdb-remote flag is set.
+ start_semaphore_.Wait();
+ return !!target_;
+}
+
+void GdbServerThread::CleanupThread() {
+ // Executed in the GdbServer thread.
+ v8::base::MutexGuard guard(&mutex_);
+
+ target_ = nullptr;
+ transport_ = nullptr;
+
+#if _WIN32
+ ::WSACleanup();
+#endif
+}
+
+void GdbServerThread::Run() {
+ // Executed in the GdbServer thread.
+#ifdef _WIN32
+ // Initialize Winsock
+ WSADATA wsaData;
+ int iResult = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
+ if (iResult != 0) {
+ TRACE_GDB_REMOTE("GdbServerThread::Run: WSAStartup failed\n");
+ return;
+ }
+#endif
+
+ // If the default port is not available, try any port.
+ SocketBinding socket_binding = SocketBinding::Bind(FLAG_wasm_gdb_remote_port);
+ if (!socket_binding.IsValid()) {
+ socket_binding = SocketBinding::Bind(0);
+ }
+ if (!socket_binding.IsValid()) {
+ TRACE_GDB_REMOTE("GdbServerThread::Run: Failed to bind any TCP port\n");
+ return;
+ }
+ TRACE_GDB_REMOTE("gdb-remote(%d) : Connect GDB with 'target remote :%d\n",
+ __LINE__, socket_binding.GetBoundPort());
+
+ transport_ = socket_binding.CreateTransport();
+ target_ = std::make_unique<Target>(gdb_server_);
+
+ // Here we have completed the initialization, and the thread that called
+ // {StartAndInitialize} may resume execution.
+ start_semaphore_.Signal();
+
+ while (!target_->IsTerminated()) {
+ // Wait for incoming connections.
+ if (!transport_->AcceptConnection()) {
+ continue;
+ }
+
+ // Create a new session for this connection
+ Session session(transport_.get());
+ TRACE_GDB_REMOTE("GdbServerThread: Connected\n");
+
+ // Run this session for as long as it lasts
+ target_->Run(&session);
+ }
+ CleanupThread();
+}
+
+void GdbServerThread::Stop() {
+ // Executed in the Isolate thread.
+
+ // Synchronized, becauses {Stop} might be called while {Run} is still
+ // initializing {transport_} and {target_}. If this happens and the thread is
+ // blocked waiting for an incoming connection or GdbServer for incoming
+ // packets, it will unblocked when {transport_} is closed.
+ v8::base::MutexGuard guard(&mutex_);
+
+ if (target_) {
+ target_->Terminate();
+ }
+
+ if (transport_) {
+ transport_->Close();
+ }
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-server-thread.h b/src/debug/wasm/gdb-server/gdb-server-thread.h
new file mode 100644
index 0000000..cca1e4a
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server-thread.h
@@ -0,0 +1,63 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
+
+#include "src/base/platform/platform.h"
+#include "src/base/platform/semaphore.h"
+#include "src/debug/wasm/gdb-server/target.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class GdbServer;
+
+// class GdbServerThread spawns a thread where all communication with a debugger
+// happens.
+class GdbServerThread : public v8::base::Thread {
+ public:
+ explicit GdbServerThread(GdbServer* gdb_server);
+
+ // base::Thread
+ void Run() override;
+
+ // Starts the GDB-server thread and waits Run() method is called on the new
+ // thread and the initialization completes.
+ bool StartAndInitialize();
+
+ // Stops the GDB-server thread when the V8 process shuts down; gracefully
+ // closes any active debugging session.
+ void Stop();
+
+ Target& GetTarget() { return *target_; }
+
+ private:
+ void CleanupThread();
+
+ GdbServer* gdb_server_;
+
+ // Used to block the caller on StartAndInitialize() waiting for the new thread
+ // to have completed its initialization.
+ // (Note that Thread::StartSynchronously() wouldn't work in this case because
+ // it returns as soon as the new thread starts, but before Run() is called).
+ base::Semaphore start_semaphore_;
+
+ base::Mutex mutex_;
+ // Protected by {mutex_}:
+ std::unique_ptr<TransportBase> transport_;
+ std::unique_ptr<Target> target_;
+
+ DISALLOW_COPY_AND_ASSIGN(GdbServerThread);
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
diff --git a/src/debug/wasm/gdb-server/gdb-server.cc b/src/debug/wasm/gdb-server/gdb-server.cc
new file mode 100644
index 0000000..96e2308
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server.cc
@@ -0,0 +1,424 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/gdb-server.h"
+
+#include <inttypes.h>
+#include <functional>
+#include "src/api/api-inl.h"
+#include "src/api/api.h"
+#include "src/debug/debug.h"
+#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
+#include "src/utils/locked-queue-inl.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+static const uint32_t kMaxWasmCallStack = 20;
+
+// A TaskRunner is an object that runs posted tasks (in the form of closure
+// objects). Tasks are queued and run, in order, in the thread where the
+// TaskRunner::RunMessageLoop() is called.
+class TaskRunner {
+ public:
+ // Class Task wraps a std::function with a semaphore to signal its completion.
+ // This logic would be neatly implemented with std::packaged_tasks but we
+ // cannot use <future> in V8.
+ class Task {
+ public:
+ Task(base::Semaphore* ready_semaphore, std::function<void()> func)
+ : ready_semaphore_(ready_semaphore), func_(func) {}
+
+ void Run() {
+ func_();
+ ready_semaphore_->Signal();
+ }
+
+ // A semaphore object passed by the thread that posts a task.
+ // The sender can Wait on this semaphore to block until the task has
+ // completed execution in the TaskRunner thread.
+ base::Semaphore* ready_semaphore_;
+
+ // The function to run.
+ std::function<void()> func_;
+ };
+
+ TaskRunner()
+ : process_queue_semaphore_(0),
+ nested_loop_count_(0),
+ is_terminated_(false) {}
+
+ // Starts the task runner. All tasks posted are run, in order, in the thread
+ // that calls this function.
+ void Run() {
+ is_terminated_ = false;
+ int loop_number = ++nested_loop_count_;
+ while (nested_loop_count_ == loop_number && !is_terminated_) {
+ std::shared_ptr<Task> task = GetNext();
+ if (task) {
+ task->Run();
+ }
+ }
+ }
+
+ // Terminates the task runner. Tasks that are still pending in the queue are
+ // not discarded and will be executed when the task runner is restarted.
+ void Terminate() {
+ DCHECK_LT(0, nested_loop_count_);
+ --nested_loop_count_;
+
+ is_terminated_ = true;
+ process_queue_semaphore_.Signal();
+ }
+
+ // Posts a task to the task runner, to be executed in the task runner thread.
+ template <typename Functor>
+ auto Append(base::Semaphore* ready_semaphore, Functor&& task) {
+ queue_.Enqueue(std::make_shared<Task>(ready_semaphore, task));
+ process_queue_semaphore_.Signal();
+ }
+
+ private:
+ std::shared_ptr<Task> GetNext() {
+ while (!is_terminated_) {
+ if (queue_.IsEmpty()) {
+ process_queue_semaphore_.Wait();
+ }
+
+ std::shared_ptr<Task> task;
+ if (queue_.Dequeue(&task)) {
+ return task;
+ }
+ }
+ return nullptr;
+ }
+
+ LockedQueue<std::shared_ptr<Task>> queue_;
+ v8::base::Semaphore process_queue_semaphore_;
+ int nested_loop_count_;
+ std::atomic<bool> is_terminated_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskRunner);
+};
+
+GdbServer::GdbServer() { task_runner_ = std::make_unique<TaskRunner>(); }
+
+template <typename Functor>
+auto GdbServer::RunSyncTask(Functor&& callback) const {
+ // Executed in the GDBServerThread.
+ v8::base::Semaphore ready_semaphore(0);
+ task_runner_->Append(&ready_semaphore, callback);
+ ready_semaphore.Wait();
+}
+
+// static
+std::unique_ptr<GdbServer> GdbServer::Create() {
+ DCHECK(FLAG_wasm_gdb_remote);
+
+ std::unique_ptr<GdbServer> gdb_server(new GdbServer());
+
+ // Spawns the GDB-stub thread where all the communication with the debugger
+ // happens.
+ gdb_server->thread_ = std::make_unique<GdbServerThread>(gdb_server.get());
+ if (!gdb_server->thread_->StartAndInitialize()) {
+ TRACE_GDB_REMOTE(
+ "Cannot initialize thread, GDB-remote debugging will be disabled.\n");
+ return nullptr;
+ }
+ return gdb_server;
+}
+
+GdbServer::~GdbServer() {
+ // All Isolates have been deregistered.
+ DCHECK(isolate_delegates_.empty());
+
+ if (thread_) {
+ // Waits for the GDB-stub thread to terminate.
+ thread_->Stop();
+ thread_->Join();
+ }
+}
+
+void GdbServer::RunMessageLoopOnPause() { task_runner_->Run(); }
+
+void GdbServer::QuitMessageLoopOnPause() { task_runner_->Terminate(); }
+
+std::vector<GdbServer::WasmModuleInfo> GdbServer::GetLoadedModules() {
+ // Executed in the GDBServerThread.
+ std::vector<GdbServer::WasmModuleInfo> modules;
+
+ RunSyncTask([this, &modules]() {
+ // Executed in the isolate thread.
+ for (const auto& pair : scripts_) {
+ uint32_t module_id = pair.first;
+ const WasmModuleDebug& module_debug = pair.second;
+ modules.push_back({module_id, module_debug.GetModuleName()});
+ }
+ });
+ return modules;
+}
+
+bool GdbServer::GetModuleDebugHandler(uint32_t module_id,
+ WasmModuleDebug** wasm_module_debug) {
+ // Always executed in the isolate thread.
+ ScriptsMap::iterator scriptIterator = scripts_.find(module_id);
+ if (scriptIterator != scripts_.end()) {
+ *wasm_module_debug = &scriptIterator->second;
+ return true;
+ }
+ wasm_module_debug = nullptr;
+ return false;
+}
+
+bool GdbServer::GetWasmGlobal(uint32_t frame_index, uint32_t index,
+ uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
+ // Executed in the isolate thread.
+ result = WasmModuleDebug::GetWasmGlobal(GetTarget().GetCurrentIsolate(),
+ frame_index, index, buffer,
+ buffer_size, size);
+ });
+ return result;
+}
+
+bool GdbServer::GetWasmLocal(uint32_t frame_index, uint32_t index,
+ uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
+ // Executed in the isolate thread.
+ result = WasmModuleDebug::GetWasmLocal(GetTarget().GetCurrentIsolate(),
+ frame_index, index, buffer,
+ buffer_size, size);
+ });
+ return result;
+}
+
+bool GdbServer::GetWasmStackValue(uint32_t frame_index, uint32_t index,
+ uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
+ // Executed in the isolate thread.
+ result = WasmModuleDebug::GetWasmStackValue(GetTarget().GetCurrentIsolate(),
+ frame_index, index, buffer,
+ buffer_size, size);
+ });
+ return result;
+}
+
+uint32_t GdbServer::GetWasmMemory(uint32_t frame_index, uint32_t offset,
+ uint8_t* buffer, uint32_t size) {
+ // Executed in the GDBServerThread.
+ uint32_t bytes_read = 0;
+ RunSyncTask([this, &bytes_read, frame_index, offset, buffer, size]() {
+ // Executed in the isolate thread.
+ bytes_read = WasmModuleDebug::GetWasmMemory(
+ GetTarget().GetCurrentIsolate(), frame_index, offset, buffer, size);
+ });
+ return bytes_read;
+}
+
+uint32_t GdbServer::GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer,
+ uint32_t size) {
+ // Executed in the GDBServerThread.
+ uint32_t bytes_read = 0;
+ RunSyncTask([this, &bytes_read, wasm_addr, buffer, size]() {
+ // Executed in the isolate thread.
+ WasmModuleDebug* module_debug;
+ if (GetModuleDebugHandler(wasm_addr.ModuleId(), &module_debug)) {
+ bytes_read = module_debug->GetWasmModuleBytes(wasm_addr, buffer, size);
+ }
+ });
+ return bytes_read;
+}
+
+bool GdbServer::AddBreakpoint(uint32_t wasm_module_id, uint32_t offset) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, wasm_module_id, offset]() {
+ // Executed in the isolate thread.
+ WasmModuleDebug* module_debug;
+ if (GetModuleDebugHandler(wasm_module_id, &module_debug)) {
+ int breakpoint_id = 0;
+ if (module_debug->AddBreakpoint(offset, &breakpoint_id)) {
+ breakpoints_[wasm_addr_t(wasm_module_id, offset)] = breakpoint_id;
+ result = true;
+ }
+ }
+ });
+ return result;
+}
+
+bool GdbServer::RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, wasm_module_id, offset]() {
+ // Executed in the isolate thread.
+ BreakpointsMap::iterator it =
+ breakpoints_.find(wasm_addr_t(wasm_module_id, offset));
+ if (it != breakpoints_.end()) {
+ int breakpoint_id = it->second;
+ breakpoints_.erase(it);
+
+ WasmModuleDebug* module_debug;
+ if (GetModuleDebugHandler(wasm_module_id, &module_debug)) {
+ module_debug->RemoveBreakpoint(offset, breakpoint_id);
+ result = true;
+ }
+ }
+ });
+ return result;
+}
+
+std::vector<wasm_addr_t> GdbServer::GetWasmCallStack() const {
+ // Executed in the GDBServerThread.
+ std::vector<wasm_addr_t> result;
+ RunSyncTask([this, &result]() {
+ // Executed in the isolate thread.
+ result = GetTarget().GetCallStack();
+ });
+ return result;
+}
+
+void GdbServer::AddIsolate(Isolate* isolate) {
+ // Executed in the isolate thread.
+ if (isolate_delegates_.find(isolate) == isolate_delegates_.end()) {
+ isolate_delegates_[isolate] =
+ std::make_unique<DebugDelegate>(isolate, this);
+ }
+}
+
+void GdbServer::RemoveIsolate(Isolate* isolate) {
+ // Executed in the isolate thread.
+ auto it = isolate_delegates_.find(isolate);
+ if (it != isolate_delegates_.end()) {
+ for (auto it = scripts_.begin(); it != scripts_.end();) {
+ if (it->second.GetIsolate() == isolate) {
+ it = scripts_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ isolate_delegates_.erase(it);
+ }
+}
+
+void GdbServer::Suspend() {
+ // Executed in the GDBServerThread.
+ auto it = isolate_delegates_.begin();
+ if (it != isolate_delegates_.end()) {
+ Isolate* isolate = it->first;
+ v8::Isolate* v8Isolate = (v8::Isolate*)isolate;
+ v8Isolate->RequestInterrupt(
+ // Executed in the isolate thread.
+ [](v8::Isolate* isolate, void*) {
+ if (v8::debug::AllFramesOnStackAreBlackboxed(isolate)) {
+ v8::debug::SetBreakOnNextFunctionCall(isolate);
+ } else {
+ v8::debug::BreakRightNow(isolate);
+ }
+ },
+ this);
+ }
+}
+
+void GdbServer::PrepareStep() {
+ // Executed in the GDBServerThread.
+ wasm_addr_t pc = GetTarget().GetCurrentPc();
+ RunSyncTask([this, pc]() {
+ // Executed in the isolate thread.
+ WasmModuleDebug* module_debug;
+ if (GetModuleDebugHandler(pc.ModuleId(), &module_debug)) {
+ module_debug->PrepareStep();
+ }
+ });
+}
+
+void GdbServer::AddWasmModule(uint32_t module_id,
+ Local<debug::WasmScript> wasm_script) {
+ // Executed in the isolate thread.
+ DCHECK_EQ(Script::TYPE_WASM, Utils::OpenHandle(*wasm_script)->type());
+ v8::Isolate* isolate = wasm_script->GetIsolate();
+ scripts_.insert(
+ std::make_pair(module_id, WasmModuleDebug(isolate, wasm_script)));
+
+ if (FLAG_wasm_pause_waiting_for_debugger && scripts_.size() == 1) {
+ TRACE_GDB_REMOTE("Paused, waiting for a debugger to attach...\n");
+ Suspend();
+ }
+}
+
+Target& GdbServer::GetTarget() const { return thread_->GetTarget(); }
+
+// static
+std::atomic<uint32_t> GdbServer::DebugDelegate::id_s;
+
+GdbServer::DebugDelegate::DebugDelegate(Isolate* isolate, GdbServer* gdb_server)
+ : isolate_(isolate), id_(id_s++), gdb_server_(gdb_server) {
+ isolate_->SetCaptureStackTraceForUncaughtExceptions(
+ true, kMaxWasmCallStack, v8::StackTrace::kOverview);
+
+ // Register the delegate
+ isolate_->debug()->SetDebugDelegate(this);
+ v8::debug::TierDownAllModulesPerIsolate((v8::Isolate*)isolate_);
+ v8::debug::ChangeBreakOnException((v8::Isolate*)isolate_,
+ v8::debug::BreakOnUncaughtException);
+}
+
+GdbServer::DebugDelegate::~DebugDelegate() {
+ // Deregister the delegate
+ isolate_->debug()->SetDebugDelegate(nullptr);
+}
+
+void GdbServer::DebugDelegate::ScriptCompiled(Local<debug::Script> script,
+ bool is_live_edited,
+ bool has_compile_error) {
+ // Executed in the isolate thread.
+ if (script->IsWasm()) {
+ DCHECK_EQ(reinterpret_cast<v8::Isolate*>(isolate_), script->GetIsolate());
+ gdb_server_->AddWasmModule(GetModuleId(script->Id()),
+ script.As<debug::WasmScript>());
+ }
+}
+
+void GdbServer::DebugDelegate::BreakProgramRequested(
+ // Executed in the isolate thread.
+ Local<v8::Context> paused_context,
+ const std::vector<debug::BreakpointId>& inspector_break_points_hit) {
+ gdb_server_->GetTarget().OnProgramBreak(
+ isolate_, WasmModuleDebug::GetCallStack(id_, isolate_));
+ gdb_server_->RunMessageLoopOnPause();
+}
+
+void GdbServer::DebugDelegate::ExceptionThrown(
+ // Executed in the isolate thread.
+ Local<v8::Context> paused_context, Local<Value> exception,
+ Local<Value> promise, bool is_uncaught,
+ debug::ExceptionType exception_type) {
+ if (exception_type == v8::debug::kException && is_uncaught) {
+ gdb_server_->GetTarget().OnException(
+ isolate_, WasmModuleDebug::GetCallStack(id_, isolate_));
+ gdb_server_->RunMessageLoopOnPause();
+ }
+}
+
+bool GdbServer::DebugDelegate::IsFunctionBlackboxed(
+ // Executed in the isolate thread.
+ Local<debug::Script> script, const debug::Location& start,
+ const debug::Location& end) {
+ return false;
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-server.h b/src/debug/wasm/gdb-server/gdb-server.h
new file mode 100644
index 0000000..50939af
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server.h
@@ -0,0 +1,201 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
+
+#include <map>
+#include <memory>
+#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
+#include "src/debug/wasm/gdb-server/wasm-module-debug.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class TaskRunner;
+
+// class GdbServer acts as a manager for the GDB-remote stub. It is instantiated
+// as soon as the first Wasm module is loaded in the Wasm engine and spawns a
+// separate thread to accept connections and exchange messages with a debugger.
+// It will contain the logic to serve debugger queries and access the state of
+// the Wasm engine.
+class GdbServer {
+ public:
+ // Factory method: creates and returns a GdbServer. Spawns a "GDB-remote"
+ // thread that will be used to communicate with the debugger.
+ // May return null on failure.
+ // This should be called once, the first time a Wasm module is loaded in the
+ // Wasm engine.
+ static std::unique_ptr<GdbServer> Create();
+
+ // Stops the "GDB-remote" thread and waits for it to complete. This should be
+ // called once, when the Wasm engine shuts down.
+ ~GdbServer();
+
+ // Queries the set of the Wasm modules currently loaded. Each module is
+ // identified by a unique integer module id.
+ struct WasmModuleInfo {
+ uint32_t module_id;
+ std::string module_name;
+ };
+ std::vector<WasmModuleInfo> GetLoadedModules();
+
+ // Queries the value of the {index} global value in the Wasm module identified
+ // by {frame_index}.
+ //
+ bool GetWasmGlobal(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Queries the value of the {index} local value in the {frame_index}th stack
+ // frame in the Wasm module identified by {frame_index}.
+ //
+ bool GetWasmLocal(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Queries the value of the {index} value in the operand stack.
+ //
+ bool GetWasmStackValue(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Reads {size} bytes, starting from {offset}, from the Memory instance
+ // associated to the Wasm module identified by {frame_index}.
+ // Returns the number of bytes copied to {buffer}, or 0 is case of error.
+ // Note: only one Memory for Module is currently supported.
+ //
+ uint32_t GetWasmMemory(uint32_t frame_index, uint32_t offset, uint8_t* buffer,
+ uint32_t size);
+
+ // Reads {size} bytes, starting from the low dword of {address}, from the Code
+ // space of th Wasm module identified by high dword of {address}.
+ // Returns the number of bytes copied to {buffer}, or 0 is case of error.
+ uint32_t GetWasmModuleBytes(wasm_addr_t address, uint8_t* buffer,
+ uint32_t size);
+
+ // Inserts a breakpoint at the offset {offset} of the Wasm module identified
+ // by {wasm_module_id}.
+ // Returns true if the breakpoint was successfully added.
+ bool AddBreakpoint(uint32_t wasm_module_id, uint32_t offset);
+
+ // Removes a breakpoint at the offset {offset} of the Wasm module identified
+ // by {wasm_module_id}.
+ // Returns true if the breakpoint was successfully removed.
+ bool RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset);
+
+ // Returns the current call stack as a vector of program counters.
+ std::vector<wasm_addr_t> GetWasmCallStack() const;
+
+ // Manage the set of Isolates for this GdbServer.
+ void AddIsolate(Isolate* isolate);
+ void RemoveIsolate(Isolate* isolate);
+
+ // Requests that the thread suspend execution at the next Wasm instruction.
+ void Suspend();
+
+ // Handle stepping in wasm functions via the wasm interpreter.
+ void PrepareStep();
+
+ // Called when the target debuggee can resume execution (for example after
+ // having been suspended on a breakpoint). Terminates the task runner leaving
+ // all pending tasks in the queue.
+ void QuitMessageLoopOnPause();
+
+ private:
+ GdbServer();
+
+ // When the target debuggee is suspended for a breakpoint or exception, blocks
+ // the main (isolate) thread and enters in a message loop. Here it waits on a
+ // queue of Task objects that are posted by the GDB-stub thread and that
+ // represent queries received from the debugger via the GDB-remote protocol.
+ void RunMessageLoopOnPause();
+
+ // Post a task to run a callback in the isolate thread.
+ template <typename Callback>
+ auto RunSyncTask(Callback&& callback) const;
+
+ void AddWasmModule(uint32_t module_id, Local<debug::WasmScript> wasm_script);
+
+ // Given a Wasm module id, retrieves the corresponding debugging WasmScript
+ // object.
+ bool GetModuleDebugHandler(uint32_t module_id,
+ WasmModuleDebug** wasm_module_debug);
+
+ // Returns the debugging target.
+ Target& GetTarget() const;
+
+ // Class DebugDelegate implements the debug::DebugDelegate interface to
+ // receive notifications when debug events happen in a given isolate, like a
+ // script being loaded, a breakpoint being hit, an exception being thrown.
+ class DebugDelegate : public debug::DebugDelegate {
+ public:
+ DebugDelegate(Isolate* isolate, GdbServer* gdb_server);
+ ~DebugDelegate();
+
+ // debug::DebugDelegate
+ void ScriptCompiled(Local<debug::Script> script, bool is_live_edited,
+ bool has_compile_error) override;
+ void BreakProgramRequested(Local<v8::Context> paused_context,
+ const std::vector<debug::BreakpointId>&
+ inspector_break_points_hit) override;
+ void ExceptionThrown(Local<v8::Context> paused_context,
+ Local<Value> exception, Local<Value> promise,
+ bool is_uncaught,
+ debug::ExceptionType exception_type) override;
+ bool IsFunctionBlackboxed(Local<debug::Script> script,
+ const debug::Location& start,
+ const debug::Location& end) override;
+
+ private:
+ // Calculates module_id as:
+ // +--------------------+------------------- +
+ // | DebugDelegate::id_ | Script::Id() |
+ // +--------------------+------------------- +
+ // <----- 16 bit -----> <----- 16 bit ----->
+ uint32_t GetModuleId(uint32_t script_id) const {
+ DCHECK_LT(script_id, 0x10000);
+ DCHECK_LT(id_, 0x10000);
+ return id_ << 16 | script_id;
+ }
+
+ Isolate* isolate_;
+ uint32_t id_;
+ GdbServer* gdb_server_;
+
+ static std::atomic<uint32_t> id_s;
+ };
+
+ // The GDB-stub thread where all the communication with the debugger happens.
+ std::unique_ptr<GdbServerThread> thread_;
+
+ // Used to transform the queries that arrive in the GDB-stub thread into
+ // tasks executed in the main (isolate) thread.
+ std::unique_ptr<TaskRunner> task_runner_;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Always accessed in the isolate thread.
+
+ // Set of breakpoints currently defines in Wasm code.
+ typedef std::map<uint64_t, int> BreakpointsMap;
+ BreakpointsMap breakpoints_;
+
+ typedef std::map<uint32_t, WasmModuleDebug> ScriptsMap;
+ ScriptsMap scripts_;
+
+ typedef std::map<Isolate*, std::unique_ptr<DebugDelegate>>
+ IsolateDebugDelegateMap;
+ IsolateDebugDelegateMap isolate_delegates_;
+
+ // End of fields always accessed in the isolate thread.
+ //////////////////////////////////////////////////////////////////////////////
+
+ DISALLOW_COPY_AND_ASSIGN(GdbServer);
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
diff --git a/src/debug/wasm/gdb-server/packet.cc b/src/debug/wasm/gdb-server/packet.cc
new file mode 100644
index 0000000..f8306c4
--- /dev/null
+++ b/src/debug/wasm/gdb-server/packet.cc
@@ -0,0 +1,364 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+Packet::Packet() {
+ seq_ = -1;
+ Clear();
+}
+
+void Packet::Clear() {
+ data_.clear();
+ read_index_ = 0;
+}
+
+void Packet::Rewind() { read_index_ = 0; }
+
+bool Packet::EndOfPacket() const { return (read_index_ >= GetPayloadSize()); }
+
+void Packet::AddRawChar(char ch) { data_.push_back(ch); }
+
+void Packet::AddWord8(uint8_t byte) {
+ char seq[2];
+ UInt8ToHex(byte, seq);
+ AddRawChar(seq[0]);
+ AddRawChar(seq[1]);
+}
+
+void Packet::AddBlock(const void* ptr, uint32_t len) {
+ DCHECK(ptr);
+
+ const char* p = (const char*)ptr;
+
+ for (uint32_t offs = 0; offs < len; offs++) {
+ AddWord8(p[offs]);
+ }
+}
+
+void Packet::AddString(const char* str) {
+ DCHECK(str);
+
+ while (*str) {
+ AddRawChar(*str);
+ str++;
+ }
+}
+
+void Packet::AddHexString(const char* str) {
+ DCHECK(str);
+
+ while (*str) {
+ AddWord8(*str);
+ str++;
+ }
+}
+
+void Packet::AddNumberSep(uint64_t val, char sep) {
+ char out[sizeof(val) * 2];
+ char temp[2];
+
+ // Check for -1 optimization
+ if (val == static_cast<uint64_t>(-1)) {
+ AddRawChar('-');
+ AddRawChar('1');
+ } else {
+ int nibbles = 0;
+
+ // In the GDB remote protocol numbers are formatted as big-endian hex
+ // strings. Leading zeros can be skipped.
+ // For example the value 0x00001234 is formatted as "1234".
+ for (size_t a = 0; a < sizeof(val); a++) {
+ uint8_t byte = static_cast<uint8_t>(val & 0xFF);
+
+ // Stream in with bytes reversed, starting with the least significant.
+ // So if we have the value 0x00001234, we store 4, then 3, 2, 1.
+ // Note that the characters are later reversed to be in big-endian order.
+ UInt8ToHex(byte, temp);
+ out[nibbles++] = temp[1];
+ out[nibbles++] = temp[0];
+
+ // Get the next 8 bits;
+ val >>= 8;
+
+ // Suppress leading zeros, so we are done when val hits zero
+ if (val == 0) {
+ break;
+ }
+ }
+
+ // Strip the high zero for this byte if present.
+ if ((nibbles > 1) && (out[nibbles - 1] == '0')) nibbles--;
+
+ // Now write it out reverse to correct the order
+ while (nibbles) {
+ nibbles--;
+ AddRawChar(out[nibbles]);
+ }
+ }
+
+ // If we asked for a separator, insert it
+ if (sep) AddRawChar(sep);
+}
+
+bool Packet::GetNumberSep(uint64_t* val, char* sep) {
+ uint64_t out = 0;
+ char ch;
+ if (!GetRawChar(&ch)) {
+ return false;
+ }
+
+ // Numbers are formatted as a big-endian hex strings.
+ // The literals "0" and "-1" as special cases.
+
+ // Check for -1
+ if (ch == '-') {
+ if (!GetRawChar(&ch)) {
+ return false;
+ }
+
+ if (ch == '1') {
+ *val = (uint64_t)-1;
+
+ ch = 0;
+ GetRawChar(&ch);
+ if (sep) {
+ *sep = ch;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ do {
+ uint8_t nib;
+
+ // Check for separator
+ if (!NibbleToUInt8(ch, &nib)) {
+ break;
+ }
+
+ // Add this nibble.
+ out = (out << 4) + nib;
+
+ // Get the next character (if availible)
+ ch = 0;
+ if (!GetRawChar(&ch)) {
+ break;
+ }
+ } while (1);
+
+ // Set the value;
+ *val = out;
+
+ // Add the separator if the user wants it...
+ if (sep != nullptr) *sep = ch;
+
+ return true;
+}
+
+bool Packet::GetRawChar(char* ch) {
+ DCHECK(ch != nullptr);
+
+ if (read_index_ >= GetPayloadSize()) return false;
+
+ *ch = data_[read_index_++];
+
+ // Check for RLE X*N, where X is the value, N is the reps.
+ if (*ch == '*') {
+ if (read_index_ < 2) {
+ TRACE_GDB_REMOTE("Unexpected RLE at start of packet.\n");
+ return false;
+ }
+
+ if (read_index_ >= GetPayloadSize()) {
+ TRACE_GDB_REMOTE("Unexpected EoP during RLE.\n");
+ return false;
+ }
+
+ // GDB does not use "CTRL" characters in the stream, so the
+ // number of reps is encoded as the ASCII value beyond 28
+ // (which when you add a min rep size of 4, forces the rep
+ // character to be ' ' (32) or greater).
+ int32_t cnt = (data_[read_index_] - 28);
+ if (cnt < 3) {
+ TRACE_GDB_REMOTE("Unexpected RLE length.\n");
+ return false;
+ }
+
+ // We have just read '*' and incremented the read pointer,
+ // so here is the old state, and expected new state.
+ //
+ // Assume N = 5, we grow by N - size of encoding (3).
+ //
+ // OldP: R W
+ // OldD: 012X*N89 = 8 chars
+ // Size: 012X*N89__ = 10 chars
+ // Move: 012X*__N89 = 10 chars
+ // Fill: 012XXXXX89 = 10 chars
+ // NewP: R W (shifted 5 - 3)
+
+ // First, store the remaining characters to the right into a temp string.
+ std::string right = data_.substr(read_index_ + 1);
+ // Discard the '*' we just read
+ data_.erase(read_index_ - 1);
+ // Append (N-1) 'X' chars
+ *ch = data_[read_index_ - 2];
+ data_.append(cnt - 1, *ch);
+ // Finally, append the remaining characters
+ data_.append(right);
+ }
+ return true;
+}
+
+bool Packet::GetWord8(uint8_t* value) {
+ DCHECK(value);
+
+ // Get two ASCII hex values and convert them to ints
+ char seq[2];
+ if (!GetRawChar(&seq[0]) || !GetRawChar(&seq[1])) {
+ return false;
+ }
+ return HexToUInt8(seq, value);
+}
+
+bool Packet::GetBlock(void* ptr, uint32_t len) {
+ DCHECK(ptr);
+
+ uint8_t* p = reinterpret_cast<uint8_t*>(ptr);
+ bool res = true;
+
+ for (uint32_t offs = 0; offs < len; offs++) {
+ res = GetWord8(&p[offs]);
+ if (false == res) {
+ break;
+ }
+ }
+
+ return res;
+}
+
+bool Packet::GetString(std::string* str) {
+ if (EndOfPacket()) {
+ return false;
+ }
+
+ *str = data_.substr(read_index_);
+ read_index_ = GetPayloadSize();
+ return true;
+}
+
+bool Packet::GetHexString(std::string* str) {
+ // Decode a string encoded as a series of 2-hex digit pairs.
+
+ if (EndOfPacket()) {
+ return false;
+ }
+
+ // Pull values until we hit a separator
+ str->clear();
+ char ch1;
+ while (GetRawChar(&ch1)) {
+ uint8_t nib1;
+ if (!NibbleToUInt8(ch1, &nib1)) {
+ read_index_--;
+ break;
+ }
+ char ch2;
+ uint8_t nib2;
+ if (!GetRawChar(&ch2) || !NibbleToUInt8(ch2, &nib2)) {
+ return false;
+ }
+ *str += static_cast<char>((nib1 << 4) + nib2);
+ }
+ return true;
+}
+
+const char* Packet::GetPayload() const { return data_.c_str(); }
+
+size_t Packet::GetPayloadSize() const { return data_.size(); }
+
+bool Packet::GetSequence(int32_t* ch) const {
+ DCHECK(ch);
+
+ if (seq_ != -1) {
+ *ch = seq_;
+ return true;
+ }
+
+ return false;
+}
+
+void Packet::ParseSequence() {
+ size_t saved_read_index = read_index_;
+ unsigned char seq;
+ char ch;
+ if (GetWord8(&seq) && GetRawChar(&ch)) {
+ if (ch == ':') {
+ SetSequence(seq);
+ return;
+ }
+ }
+ // No sequence number present, so reset to original position.
+ read_index_ = saved_read_index;
+}
+
+void Packet::SetSequence(int32_t val) { seq_ = val; }
+
+void Packet::SetError(ErrDef error) {
+ Clear();
+ AddRawChar('E');
+ AddWord8(static_cast<uint8_t>(error));
+}
+
+std::string Packet::GetPacketData() const {
+ char chars[2];
+ const char* ptr = GetPayload();
+ size_t size = GetPayloadSize();
+
+ std::stringstream outstr;
+
+ // Signal start of response
+ outstr << '$';
+
+ char run_xsum = 0;
+
+ // If there is a sequence, send as two nibble 8bit value + ':'
+ int32_t seq;
+ if (GetSequence(&seq)) {
+ UInt8ToHex(seq, chars);
+ outstr << chars[0];
+ run_xsum += chars[0];
+ outstr << chars[1];
+ run_xsum += chars[1];
+
+ outstr << ':';
+ run_xsum += ':';
+ }
+
+ // Send the main payload
+ for (size_t offs = 0; offs < size; ++offs) {
+ outstr << ptr[offs];
+ run_xsum += ptr[offs];
+ }
+
+ // Send XSUM as two nibble 8bit value preceeded by '#'
+ outstr << '#';
+ UInt8ToHex(run_xsum, chars);
+ outstr << chars[0];
+ outstr << chars[1];
+
+ return outstr.str();
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/packet.h b/src/debug/wasm/gdb-server/packet.h
new file mode 100644
index 0000000..4308081
--- /dev/null
+++ b/src/debug/wasm/gdb-server/packet.h
@@ -0,0 +1,105 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
+#define V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
+
+#include <string>
+#include <vector>
+#include "src/base/macros.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class V8_EXPORT_PRIVATE Packet {
+ public:
+ Packet();
+
+ // Empty the vector and reset the read/write pointers.
+ void Clear();
+
+ // Reset the read pointer, allowing the packet to be re-read.
+ void Rewind();
+
+ // Return true of the read pointer has reached the write pointer.
+ bool EndOfPacket() const;
+
+ // Store a single raw 8 bit value
+ void AddRawChar(char ch);
+
+ // Store a block of data as hex pairs per byte
+ void AddBlock(const void* ptr, uint32_t len);
+
+ // Store a byte as a 2 chars block.
+ void AddWord8(uint8_t val);
+
+ // Store a number up to 64 bits, formatted as a big-endian hex string with
+ // preceeding zeros removed. Since zeros can be removed, the width of this
+ // number is unknown, and the number is always followed by a NULL or a
+ // separator (non hex digit).
+ void AddNumberSep(uint64_t val, char sep);
+
+ // Add a raw string.
+ void AddString(const char* str);
+
+ // Add a string stored as a stream of ASCII hex digit pairs. It is safe
+ // to use any non-null character in this stream. If this does not terminate
+ // the packet, there should be a separator (non hex digit) immediately
+ // following.
+ void AddHexString(const char* str);
+
+ // Retrieve a single character if available
+ bool GetRawChar(char* ch);
+
+ // Retrieve "len" ASCII character pairs.
+ bool GetBlock(void* ptr, uint32_t len);
+
+ // Retrieve a 8, 16, 32, or 64 bit word as pairs of hex digits. These
+ // functions will always consume bits/4 characters from the stream.
+ bool GetWord8(uint8_t* val);
+
+ // Retrieve a number (formatted as a big-endian hex string) and a separator.
+ // If 'sep' is null, the separator is consumed but thrown away.
+ bool GetNumberSep(uint64_t* val, char* sep);
+
+ // Get a string from the stream
+ bool GetString(std::string* str);
+ bool GetHexString(std::string* str);
+
+ // Return a pointer to the entire packet payload
+ const char* GetPayload() const;
+ size_t GetPayloadSize() const;
+
+ // Returns true and the sequence number, or false if it is unset.
+ bool GetSequence(int32_t* seq) const;
+
+ // Parses sequence number in package data and moves read pointer past it.
+ void ParseSequence();
+
+ // Set the sequence number.
+ void SetSequence(int32_t seq);
+
+ enum class ErrDef { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 };
+ void SetError(ErrDef);
+
+ // Returns the full content of a GDB-remote packet, in the format:
+ // $payload#checksum
+ // where the two-digit checksum is computed as the modulo 256 sum of all
+ // characters between the leading ‘$’ and the trailing ‘#’.
+ std::string GetPacketData() const;
+
+ private:
+ int32_t seq_;
+ std::string data_;
+ size_t read_index_;
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
diff --git a/src/debug/wasm/gdb-server/session.cc b/src/debug/wasm/gdb-server/session.cc
new file mode 100644
index 0000000..b052934
--- /dev/null
+++ b/src/debug/wasm/gdb-server/session.cc
@@ -0,0 +1,148 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/session.h"
+#include "src/debug/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+Session::Session(TransportBase* transport)
+ : io_(transport), connected_(true), ack_enabled_(true) {}
+
+void Session::WaitForDebugStubEvent() { io_->WaitForDebugStubEvent(); }
+
+bool Session::SignalThreadEvent() { return io_->SignalThreadEvent(); }
+
+bool Session::IsDataAvailable() const { return io_->IsDataAvailable(); }
+
+bool Session::IsConnected() const { return connected_; }
+
+void Session::Disconnect() {
+ io_->Disconnect();
+ connected_ = false;
+}
+
+bool Session::GetChar(char* ch) {
+ if (!io_->Read(ch, 1)) {
+ Disconnect();
+ return false;
+ }
+
+ return true;
+}
+
+bool Session::SendPacket(Packet* pkt, bool expect_ack) {
+ char ch;
+ do {
+ std::string data = pkt->GetPacketData();
+
+ TRACE_GDB_REMOTE("TX %s\n", data.size() < 160
+ ? data.c_str()
+ : (data.substr(0, 160) + "...").c_str());
+ if (!io_->Write(data.data(), static_cast<int32_t>(data.length()))) {
+ return false;
+ }
+
+ // If ACKs are off, we are done.
+ if (!expect_ack || !ack_enabled_) {
+ break;
+ }
+
+ // Otherwise, poll for '+'
+ if (!GetChar(&ch)) {
+ return false;
+ }
+
+ // Retry if we didn't get a '+'
+ } while (ch != '+');
+
+ return true;
+}
+
+bool Session::GetPayload(Packet* pkt, uint8_t* checksum) {
+ pkt->Clear();
+ *checksum = 0;
+
+ // Stream in the characters
+ char ch;
+ while (GetChar(&ch)) {
+ if (ch == '#') {
+ // If we see a '#' we must be done with the data.
+ return true;
+ } else if (ch == '$') {
+ // If we see a '$' we must have missed the last cmd, let's retry.
+ TRACE_GDB_REMOTE("RX Missing $, retry.\n");
+ *checksum = 0;
+ pkt->Clear();
+ } else {
+ // Keep a running XSUM.
+ *checksum += ch;
+ pkt->AddRawChar(ch);
+ }
+ }
+ return false;
+}
+
+bool Session::GetPacket(Packet* pkt) {
+ while (true) {
+ // Toss characters until we see a start of command
+ char ch;
+ do {
+ if (!GetChar(&ch)) {
+ return false;
+ }
+ } while (ch != '$');
+
+ uint8_t running_checksum = 0;
+ if (!GetPayload(pkt, &running_checksum)) {
+ return false;
+ }
+
+ // Get two nibble checksum
+ uint8_t trailing_checksum = 0;
+ char chars[2];
+ if (!GetChar(&chars[0]) || !GetChar(&chars[1]) ||
+ !HexToUInt8(chars, &trailing_checksum)) {
+ return false;
+ }
+
+ TRACE_GDB_REMOTE("RX $%s#%c%c\n", pkt->GetPayload(), chars[0], chars[1]);
+
+ pkt->ParseSequence();
+
+ // If ACKs are off, we are done.
+ if (!ack_enabled_) {
+ return true;
+ }
+
+ // If the XSUMs don't match, signal bad packet
+ if (trailing_checksum == running_checksum) {
+ char out[3] = {'+', 0, 0};
+
+ // If we have a sequence number
+ int32_t seq;
+ if (pkt->GetSequence(&seq)) {
+ // Respond with sequence number
+ UInt8ToHex(seq, &out[1]);
+ return io_->Write(out, 3);
+ } else {
+ return io_->Write(out, 1);
+ }
+ } else {
+ // Resend a bad XSUM and look for retransmit
+ TRACE_GDB_REMOTE("RX Bad XSUM, retry\n");
+ io_->Write("-", 1);
+ // retry...
+ }
+ }
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/session.h b/src/debug/wasm/gdb-server/session.h
new file mode 100644
index 0000000..d7c2263
--- /dev/null
+++ b/src/debug/wasm/gdb-server/session.h
@@ -0,0 +1,73 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
+#define V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
+
+#include "src/base/macros.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class Packet;
+class TransportBase;
+
+// Represents a gdb-remote debugging session.
+class V8_EXPORT_PRIVATE Session {
+ public:
+ explicit Session(TransportBase* transport);
+
+ // Attempt to send a packet and optionally wait for an ACK from the receiver.
+ bool SendPacket(Packet* packet, bool expect_ack = true);
+
+ // Attempt to receive a packet.
+ bool GetPacket(Packet* packet);
+
+ // Return true if there is data to read.
+ bool IsDataAvailable() const;
+
+ // Return true if the connection is still valid.
+ bool IsConnected() const;
+
+ // Shutdown the connection.
+ void Disconnect();
+
+ // When a debugging session is active, the GDB-remote thread can block waiting
+ // for events and it will resume execution when one of these two events arise:
+ // - A network event (a new packet arrives, or the connection is dropped)
+ // - A thread event (the execution stopped because of a trap or breakpoint).
+ void WaitForDebugStubEvent();
+
+ // Signal that the debuggee execution stopped because of a trap or breakpoint.
+ bool SignalThreadEvent();
+
+ // By default, when either the debugger or the GDB-stub sends a packet,
+ // the first response expected is an acknowledgment: either '+' (to indicate
+ // the packet was received correctly) or '-' (to request retransmission).
+ // When a transport is reliable, the debugger may request that acknowledgement
+ // be disabled by means of the 'QStartNoAckMode' packet.
+ void EnableAck(bool ack_enabled) { ack_enabled_ = ack_enabled; }
+
+ private:
+ // Read a single character from the transport.
+ bool GetChar(char* ch);
+
+ // Read the content of a packet, from a leading '$' to a trailing '#'.
+ bool GetPayload(Packet* pkt, uint8_t* checksum);
+
+ TransportBase* io_; // Transport object not owned by the Session.
+ bool connected_; // Is the connection still valid.
+ bool ack_enabled_; // If true, emit or wait for '+' from RSP stream.
+
+ DISALLOW_COPY_AND_ASSIGN(Session);
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
diff --git a/src/debug/wasm/gdb-server/target.cc b/src/debug/wasm/gdb-server/target.cc
new file mode 100644
index 0000000..6992fd1
--- /dev/null
+++ b/src/debug/wasm/gdb-server/target.cc
@@ -0,0 +1,679 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/target.h"
+
+#include <inttypes.h>
+#include "src/base/platform/time.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+#include "src/debug/wasm/gdb-server/gdb-server.h"
+#include "src/debug/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/session.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+static const int kThreadId = 1;
+
+// Signals.
+static const int kSigTrace = 5;
+static const int kSigSegv = 11;
+
+Target::Target(GdbServer* gdb_server)
+ : gdb_server_(gdb_server),
+ status_(Status::Running),
+ cur_signal_(0),
+ session_(nullptr),
+ debugger_initial_suspension_(true),
+ semaphore_(0),
+ current_isolate_(nullptr) {
+ InitQueryPropertyMap();
+}
+
+void Target::InitQueryPropertyMap() {
+ // Request LLDB to send packets up to 4000 bytes for bulk transfers.
+ query_properties_["Supported"] =
+ "PacketSize=1000;vContSupported-;qXfer:libraries:read+;";
+
+ query_properties_["Attached"] = "1";
+
+ // There is only one register, named 'pc', in this architecture
+ query_properties_["RegisterInfo0"] =
+ "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:"
+ "General Purpose Registers;gcc:16;dwarf:16;generic:pc;";
+ query_properties_["RegisterInfo1"] = "E45";
+
+ // ProcessInfo for wasm32
+ query_properties_["ProcessInfo"] =
+ "pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:6c6c6462;triple:" +
+ Mem2Hex("wasm32-unknown-unknown-wasm") + ";ptrsize:4;";
+ query_properties_["Symbol"] = "OK";
+
+ // Current thread info
+ char buff[16];
+ snprintf(buff, sizeof(buff), "QC%x", kThreadId);
+ query_properties_["C"] = buff;
+}
+
+void Target::Terminate() {
+ // Executed in the Isolate thread, when the process shuts down.
+ SetStatus(Status::Terminated);
+}
+
+void Target::OnProgramBreak(Isolate* isolate,
+ const std::vector<wasm_addr_t>& call_frames) {
+ OnSuspended(isolate, kSigTrace, call_frames);
+}
+void Target::OnException(Isolate* isolate,
+ const std::vector<wasm_addr_t>& call_frames) {
+ OnSuspended(isolate, kSigSegv, call_frames);
+}
+void Target::OnSuspended(Isolate* isolate, int signal,
+ const std::vector<wasm_addr_t>& call_frames) {
+ // This function will be called in the isolate thread, when the wasm
+ // interpreter gets suspended.
+
+ bool isWaitingForSuspension = (status_ == Status::WaitingForSuspension);
+ SetStatus(Status::Suspended, signal, call_frames, isolate);
+ if (isWaitingForSuspension) {
+ // Wake the GdbServer thread that was blocked waiting for the Target
+ // to suspend.
+ semaphore_.Signal();
+ } else if (session_) {
+ session_->SignalThreadEvent();
+ }
+}
+
+void Target::Run(Session* session) {
+ // Executed in the GdbServer thread.
+ session_ = session;
+ do {
+ WaitForDebugEvent();
+ ProcessDebugEvent();
+ ProcessCommands();
+ } while (!IsTerminated() && session_->IsConnected());
+ session_ = nullptr;
+}
+
+void Target::WaitForDebugEvent() {
+ // Executed in the GdbServer thread.
+
+ if (status_ == Status::Running) {
+ // Wait for either:
+ // * the thread to fault (or single-step)
+ // * an interrupt from LLDB
+ session_->WaitForDebugStubEvent();
+ }
+}
+
+void Target::ProcessDebugEvent() {
+ // Executed in the GdbServer thread
+
+ if (status_ == Status::Running) {
+ // Blocks, waiting for the engine to suspend.
+ Suspend();
+ }
+
+ // Here, the wasm interpreter has suspended and we have updated the current
+ // thread info.
+
+ if (debugger_initial_suspension_) {
+ // First time on a connection, we don't send the signal.
+ // All other times, send the signal that triggered us.
+ debugger_initial_suspension_ = false;
+ } else {
+ Packet pktOut;
+ SetStopReply(&pktOut);
+ session_->SendPacket(&pktOut, false);
+ }
+}
+
+void Target::Suspend() {
+ // Executed in the GdbServer thread
+ if (status_ == Status::Running) {
+ // TODO(paolosev) - this only suspends the wasm interpreter.
+ gdb_server_->Suspend();
+
+ status_ = Status::WaitingForSuspension;
+ }
+
+ while (status_ == Status::WaitingForSuspension) {
+ if (semaphore_.WaitFor(base::TimeDelta::FromMilliseconds(500))) {
+ // Here the wasm interpreter is suspended.
+ return;
+ }
+ }
+}
+
+void Target::ProcessCommands() {
+ // GDB-remote messages are processed in the GDBServer thread.
+
+ if (IsTerminated()) {
+ return;
+ } else if (status_ != Status::Suspended) {
+ // Don't process commands if we haven't stopped.
+ return;
+ }
+
+ // Now we are ready to process commands.
+ // Loop through packets until we process a continue packet or a detach.
+ Packet recv, reply;
+ while (session_->IsConnected()) {
+ if (!session_->GetPacket(&recv)) {
+ continue;
+ }
+
+ reply.Clear();
+ ProcessPacketResult result = ProcessPacket(&recv, &reply);
+ switch (result) {
+ case ProcessPacketResult::Paused:
+ session_->SendPacket(&reply);
+ break;
+
+ case ProcessPacketResult::Continue:
+ DCHECK_EQ(status_, Status::Running);
+ // If this is a continue type command, break out of this loop.
+ gdb_server_->QuitMessageLoopOnPause();
+ return;
+
+ case ProcessPacketResult::Detach:
+ SetStatus(Status::Running);
+ session_->SendPacket(&reply);
+ session_->Disconnect();
+ gdb_server_->QuitMessageLoopOnPause();
+ return;
+
+ case ProcessPacketResult::Kill:
+ session_->SendPacket(&reply);
+ exit(-9);
+
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ if (!session_->IsConnected()) {
+ debugger_initial_suspension_ = true;
+ }
+}
+
+Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
+ Packet* pkt_out) {
+ ErrorCode err = ErrorCode::None;
+
+ // Clear the outbound message.
+ pkt_out->Clear();
+
+ // Set the sequence number, if present.
+ int32_t seq = -1;
+ if (pkt_in->GetSequence(&seq)) {
+ pkt_out->SetSequence(seq);
+ }
+
+ // A GDB-remote packet begins with an upper- or lower-case letter, which
+ // generally represents a single command.
+ // The letters 'q' and 'Q' introduce a "General query packets" and are used
+ // to extend the protocol with custom commands.
+ // The format of GDB-remote commands is documented here:
+ // https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview.
+ char cmd;
+ pkt_in->GetRawChar(&cmd);
+
+ switch (cmd) {
+ // Queries the reason the target halted.
+ // IN : $?
+ // OUT: A Stop-reply packet
+ case '?':
+ SetStopReply(pkt_out);
+ break;
+
+ // Resumes execution
+ // IN : $c
+ // OUT: A Stop-reply packet is sent later, when the execution halts.
+ case 'c':
+ SetStatus(Status::Running);
+ return ProcessPacketResult::Continue;
+
+ // Detaches the debugger from this target
+ // IN : $D
+ // OUT: $OK
+ case 'D':
+ TRACE_GDB_REMOTE("Requested Detach.\n");
+ pkt_out->AddString("OK");
+ return ProcessPacketResult::Detach;
+
+ // Read general registers (We only support register 'pc' that contains
+ // the current instruction pointer).
+ // IN : $g
+ // OUT: $xx...xx
+ case 'g': {
+ uint64_t pc = GetCurrentPc();
+ pkt_out->AddBlock(&pc, sizeof(pc));
+ break;
+ }
+
+ // Write general registers - NOT SUPPORTED
+ // IN : $Gxx..xx
+ // OUT: $ (empty string)
+ case 'G': {
+ break;
+ }
+
+ // Set thread for subsequent operations. For Wasm targets, we currently
+ // assume that there is only one thread with id = kThreadId (= 1).
+ // IN : $H(c/g)(-1,0,xxxx)
+ // OUT: $OK
+ case 'H': {
+ // Type of the operation (‘m’, ‘M’, ‘g’, ‘G’, ...)
+ char operation;
+ if (!pkt_in->GetRawChar(&operation)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ uint64_t thread_id;
+ if (!pkt_in->GetNumberSep(&thread_id, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ // Ignore, only one thread supported for now.
+ pkt_out->AddString("OK");
+ break;
+ }
+
+ // Kills the debuggee.
+ // IN : $k
+ // OUT: $OK
+ case 'k':
+ TRACE_GDB_REMOTE("Requested Kill.\n");
+ pkt_out->AddString("OK");
+ return ProcessPacketResult::Kill;
+
+ // Reads {llll} addressable memory units starting at address {aaaa}.
+ // IN : $maaaa,llll
+ // OUT: $xx..xx
+ case 'm': {
+ uint64_t address;
+ if (!pkt_in->GetNumberSep(&address, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+ wasm_addr_t wasm_addr(address);
+
+ uint64_t len;
+ if (!pkt_in->GetNumberSep(&len, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ if (len > Transport::kBufSize / 2) {
+ err = ErrorCode::BadArgs;
+ break;
+ }
+
+ uint32_t length = static_cast<uint32_t>(len);
+ uint8_t buff[Transport::kBufSize];
+ if (wasm_addr.ModuleId() > 0) {
+ uint32_t read =
+ gdb_server_->GetWasmModuleBytes(wasm_addr, buff, length);
+ if (read > 0) {
+ pkt_out->AddBlock(buff, read);
+ } else {
+ err = ErrorCode::Failed;
+ }
+ } else {
+ err = ErrorCode::BadArgs;
+ }
+ break;
+ }
+
+ // Writes {llll} addressable memory units starting at address {aaaa}.
+ // IN : $Maaaa,llll:xx..xx
+ // OUT: $OK
+ case 'M': {
+ // Writing to memory not supported for Wasm.
+ err = ErrorCode::Failed;
+ break;
+ }
+
+ // pN: Reads the value of register N.
+ // IN : $pxx
+ // OUT: $xx..xx
+ case 'p': {
+ uint64_t pc = GetCurrentPc();
+ pkt_out->AddBlock(&pc, sizeof(pc));
+ } break;
+
+ case 'q': {
+ err = ProcessQueryPacket(pkt_in, pkt_out);
+ break;
+ }
+
+ // Single step
+ // IN : $s
+ // OUT: A Stop-reply packet is sent later, when the execution halts.
+ case 's': {
+ if (status_ == Status::Suspended) {
+ gdb_server_->PrepareStep();
+ SetStatus(Status::Running);
+ }
+ return ProcessPacketResult::Continue;
+ }
+
+ // Find out if the thread 'id' is alive.
+ // IN : $T
+ // OUT: $OK if alive, $Enn if thread is dead.
+ case 'T': {
+ uint64_t id;
+ if (!pkt_in->GetNumberSep(&id, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+ if (id != kThreadId) {
+ err = ErrorCode::BadArgs;
+ break;
+ }
+ pkt_out->AddString("OK");
+ break;
+ }
+
+ // Z: Adds a breakpoint
+ // IN : $Z<type>,<addr>,<kind>
+ // <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
+ // OUT: $OK (success) or $Enn (error)
+ case 'Z': {
+ uint64_t breakpoint_type;
+ uint64_t breakpoint_address;
+ uint64_t breakpoint_kind;
+ // Only software breakpoints are supported.
+ if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
+ !pkt_in->GetNumberSep(&breakpoint_address, 0) ||
+ !pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
+ if (!gdb_server_->AddBreakpoint(wasm_breakpoint_addr.ModuleId(),
+ wasm_breakpoint_addr.Offset())) {
+ err = ErrorCode::Failed;
+ break;
+ }
+
+ pkt_out->AddString("OK");
+ break;
+ }
+
+ // z: Removes a breakpoint
+ // IN : $z<type>,<addr>,<kind>
+ // <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
+ // OUT: $OK (success) or $Enn (error)
+ case 'z': {
+ uint64_t breakpoint_type;
+ uint64_t breakpoint_address;
+ uint64_t breakpoint_kind;
+ if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
+ !pkt_in->GetNumberSep(&breakpoint_address, 0) ||
+ !pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
+ if (!gdb_server_->RemoveBreakpoint(wasm_breakpoint_addr.ModuleId(),
+ wasm_breakpoint_addr.Offset())) {
+ err = ErrorCode::Failed;
+ break;
+ }
+
+ pkt_out->AddString("OK");
+ break;
+ }
+
+ // If the command is not recognized, ignore it by sending an empty reply.
+ default: {
+ TRACE_GDB_REMOTE("Unknown command: %s\n", pkt_in->GetPayload());
+ }
+ }
+
+ // If there is an error, return the error code instead of a payload
+ if (err != ErrorCode::None) {
+ pkt_out->Clear();
+ pkt_out->AddRawChar('E');
+ pkt_out->AddWord8(static_cast<uint8_t>(err));
+ }
+ return ProcessPacketResult::Paused;
+}
+
+Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
+ Packet* pkt_out) {
+ const char* str = &pkt_in->GetPayload()[1];
+
+ // Get first thread query
+ // IN : $qfThreadInfo
+ // OUT: $m<tid>
+ //
+ // Get next thread query
+ // IN : $qsThreadInfo
+ // OUT: $m<tid> or l to denote end of list.
+ if (!strcmp(str, "fThreadInfo") || !strcmp(str, "sThreadInfo")) {
+ if (str[0] == 'f') {
+ pkt_out->AddString("m");
+ pkt_out->AddNumberSep(kThreadId, 0);
+ } else {
+ pkt_out->AddString("l");
+ }
+ return ErrorCode::None;
+ }
+
+ // Get a list of loaded libraries
+ // IN : $qXfer:libraries:read
+ // OUT: an XML document which lists loaded libraries, with this format:
+ // <library-list>
+ // <library name="foo.wasm">
+ // <section address="0x100000000"/>
+ // </library>
+ // <library name="bar.wasm">
+ // <section address="0x200000000"/>
+ // </library>
+ // </library-list>
+ // Note that LLDB must be compiled with libxml2 support to handle this packet.
+ std::string tmp = "Xfer:libraries:read";
+ if (!strncmp(str, tmp.data(), tmp.length())) {
+ std::vector<GdbServer::WasmModuleInfo> modules =
+ gdb_server_->GetLoadedModules();
+ std::string result("l<library-list>");
+ for (const auto& module : modules) {
+ wasm_addr_t address(module.module_id, 0);
+ char address_string[32];
+ snprintf(address_string, sizeof(address_string), "%" PRIu64,
+ static_cast<uint64_t>(address));
+ result += "<library name=\"";
+ result += module.module_name;
+ result += "\"><section address=\"";
+ result += address_string;
+ result += "\"/></library>";
+ }
+ result += "</library-list>";
+ pkt_out->AddString(result.c_str());
+ return ErrorCode::None;
+ }
+
+ // Get the current call stack.
+ // IN : $qWasmCallStack
+ // OUT: $xx..xxyy..yyzz..zz (A sequence of uint64_t values represented as
+ // consecutive 8-bytes blocks).
+ std::vector<std::string> toks = StringSplit(str, ":;");
+ if (toks[0] == "WasmCallStack") {
+ std::vector<wasm_addr_t> call_stack_pcs = gdb_server_->GetWasmCallStack();
+ std::vector<uint64_t> buffer;
+ for (wasm_addr_t pc : call_stack_pcs) {
+ buffer.push_back(pc);
+ }
+ pkt_out->AddBlock(buffer.data(),
+ static_cast<uint32_t>(sizeof(uint64_t) * buffer.size()));
+ return ErrorCode::None;
+ }
+
+ // Get a Wasm global value in the Wasm module specified.
+ // IN : $qWasmGlobal:frame_index;index
+ // OUT: $xx..xx
+ if (toks[0] == "WasmGlobal") {
+ if (toks.size() == 3) {
+ uint32_t frame_index =
+ static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+ uint32_t index =
+ static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+ uint8_t buff[16];
+ uint32_t size = 0;
+ if (gdb_server_->GetWasmGlobal(frame_index, index, buff, 16, &size)) {
+ pkt_out->AddBlock(buff, size);
+ return ErrorCode::None;
+ } else {
+ return ErrorCode::Failed;
+ }
+ }
+ return ErrorCode::BadFormat;
+ }
+
+ // Get a Wasm local value in the stack frame specified.
+ // IN : $qWasmLocal:frame_index;index
+ // OUT: $xx..xx
+ if (toks[0] == "WasmLocal") {
+ if (toks.size() == 3) {
+ uint32_t frame_index =
+ static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+ uint32_t index =
+ static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+ uint8_t buff[16];
+ uint32_t size = 0;
+ if (gdb_server_->GetWasmLocal(frame_index, index, buff, 16, &size)) {
+ pkt_out->AddBlock(buff, size);
+ return ErrorCode::None;
+ } else {
+ return ErrorCode::Failed;
+ }
+ }
+ return ErrorCode::BadFormat;
+ }
+
+ // Get a Wasm local from the operand stack at the index specified.
+ // IN : qWasmStackValue:frame_index;index
+ // OUT: $xx..xx
+ if (toks[0] == "WasmStackValue") {
+ if (toks.size() == 3) {
+ uint32_t frame_index =
+ static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+ uint32_t index =
+ static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+ uint8_t buff[16];
+ uint32_t size = 0;
+ if (gdb_server_->GetWasmStackValue(frame_index, index, buff, 16, &size)) {
+ pkt_out->AddBlock(buff, size);
+ return ErrorCode::None;
+ } else {
+ return ErrorCode::Failed;
+ }
+ }
+ return ErrorCode::BadFormat;
+ }
+
+ // Read Wasm memory.
+ // IN : $qWasmMem:frame_index;addr;len
+ // OUT: $xx..xx
+ if (toks[0] == "WasmMem") {
+ if (toks.size() == 4) {
+ uint32_t frame_index =
+ static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+ uint32_t address =
+ static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 16));
+ uint32_t length =
+ static_cast<uint32_t>(strtol(toks[3].data(), nullptr, 16));
+ if (length > Transport::kBufSize / 2) {
+ return ErrorCode::BadArgs;
+ }
+ uint8_t buff[Transport::kBufSize];
+ uint32_t read =
+ gdb_server_->GetWasmMemory(frame_index, address, buff, length);
+ if (read > 0) {
+ pkt_out->AddBlock(buff, read);
+ return ErrorCode::None;
+ } else {
+ return ErrorCode::Failed;
+ }
+ }
+ return ErrorCode::BadFormat;
+ }
+
+ // No match so far, check the property cache.
+ QueryPropertyMap::const_iterator it = query_properties_.find(toks[0]);
+ if (it != query_properties_.end()) {
+ pkt_out->AddString(it->second.data());
+ }
+ // If not found, just send an empty response.
+ return ErrorCode::None;
+}
+
+// A Stop-reply packet has the format:
+// Sxx
+// or:
+// Txx<name1>:<value1>;...;<nameN>:<valueN>
+// where 'xx' is a two-digit hex number that represents the stop signal
+// and the <name>:<value> pairs are used to report additional information,
+// like the thread id.
+void Target::SetStopReply(Packet* pkt_out) const {
+ pkt_out->AddRawChar('T');
+ pkt_out->AddWord8(cur_signal_);
+
+ // Adds 'thread-pcs:<pc1>,...,<pcN>;' A list of pc values for all threads that
+ // currently exist in the process.
+ char buff[64];
+ snprintf(buff, sizeof(buff), "thread-pcs:%" PRIx64 ";",
+ static_cast<uint64_t>(GetCurrentPc()));
+ pkt_out->AddString(buff);
+
+ // Adds 'thread:<tid>;' pair. Note that a terminating ';' is required.
+ pkt_out->AddString("thread:");
+ pkt_out->AddNumberSep(kThreadId, ';');
+}
+
+void Target::SetStatus(Status status, int8_t signal,
+ std::vector<wasm_addr_t> call_frames, Isolate* isolate) {
+ v8::base::MutexGuard guard(&mutex_);
+
+ DCHECK((status == Status::Suspended && signal != 0 &&
+ call_frames.size() > 0 && isolate != nullptr) ||
+ (status != Status::Suspended && signal == 0 &&
+ call_frames.size() == 0 && isolate == nullptr));
+
+ current_isolate_ = isolate;
+ status_ = status;
+ cur_signal_ = signal;
+ call_frames_ = call_frames;
+}
+
+const std::vector<wasm_addr_t> Target::GetCallStack() const {
+ v8::base::MutexGuard guard(&mutex_);
+
+ return call_frames_;
+}
+
+wasm_addr_t Target::GetCurrentPc() const {
+ v8::base::MutexGuard guard(&mutex_);
+
+ wasm_addr_t pc{0};
+ if (call_frames_.size() > 0) {
+ pc = call_frames_[0];
+ }
+ return pc;
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/target.h b/src/debug/wasm/gdb-server/target.h
new file mode 100644
index 0000000..1af81d3
--- /dev/null
+++ b/src/debug/wasm/gdb-server/target.h
@@ -0,0 +1,140 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
+#define V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
+
+#include <atomic>
+#include <map>
+#include "src/base/macros.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class GdbServer;
+class Packet;
+class Session;
+
+// Class Target represents a debugging target. It contains the logic to decode
+// incoming GDB-remote packets, execute them forwarding the debugger commands
+// and queries to the Wasm engine, and send back GDB-remote packets.
+class Target {
+ public:
+ // Contruct a Target object.
+ explicit Target(GdbServer* gdb_server);
+
+ // This function spin on a debugging session, until it closes.
+ void Run(Session* ses);
+
+ void Terminate();
+ bool IsTerminated() const { return status_ == Status::Terminated; }
+
+ // Notifies that the debuggee thread suspended at a breakpoint.
+ void OnProgramBreak(Isolate* isolate,
+ const std::vector<wasm_addr_t>& call_frames);
+ // Notifies that the debuggee thread suspended because of an unhandled
+ // exception.
+ void OnException(Isolate* isolate,
+ const std::vector<wasm_addr_t>& call_frames);
+
+ // Returns the state at the moment of the thread suspension.
+ const std::vector<wasm_addr_t> GetCallStack() const;
+ wasm_addr_t GetCurrentPc() const;
+ Isolate* GetCurrentIsolate() const { return current_isolate_; }
+
+ private:
+ void OnSuspended(Isolate* isolate, int signal,
+ const std::vector<wasm_addr_t>& call_frames);
+
+ // Initializes a map used to make fast lookups when handling query packets
+ // that have a constant response.
+ void InitQueryPropertyMap();
+
+ // Blocks waiting for one of these two events to occur:
+ // - A network packet arrives from the debugger, or the debugger connection is
+ // closed;
+ // - The debuggee suspends execution because of a trap or breakpoint.
+ void WaitForDebugEvent();
+ void ProcessDebugEvent();
+
+ // Processes GDB-remote packets that arrive from the debugger.
+ // This method should be called when the debuggee has suspended its execution.
+ void ProcessCommands();
+
+ // Requests that the thread suspends execution at the next Wasm instruction.
+ void Suspend();
+
+ enum class ErrorCode { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 };
+
+ enum class ProcessPacketResult {
+ Paused, // The command was processed, debuggee still paused.
+ Continue, // The debuggee should resume execution.
+ Detach, // Request to detach from the debugger.
+ Kill // Request to terminate the debuggee process.
+ };
+ // This function always succeedes, since all errors are reported as an error
+ // string "Exx" where xx is a two digit number.
+ // The return value indicates if the target can resume execution or it is
+ // still paused.
+ ProcessPacketResult ProcessPacket(Packet* pkt_in, Packet* pkt_out);
+
+ // Processes a general query packet
+ ErrorCode ProcessQueryPacket(const Packet* pkt_in, Packet* pkt_out);
+
+ // Formats a 'Stop-reply' packet, which is sent in response of a 'c'
+ // (continue), 's' (step) and '?' (query halt reason) commands.
+ void SetStopReply(Packet* pkt_out) const;
+
+ enum class Status { Running, WaitingForSuspension, Suspended, Terminated };
+
+ void SetStatus(Status status, int8_t signal = 0,
+ std::vector<wasm_addr_t> call_frames_ = {},
+ Isolate* isolate = nullptr);
+
+ GdbServer* gdb_server_;
+
+ std::atomic<Status> status_;
+
+ // Signal being processed.
+ std::atomic<int8_t> cur_signal_;
+
+ // Session object not owned by the Target.
+ Session* session_;
+
+ // Map used to make fast lookups when handling query packets.
+ typedef std::map<std::string, std::string> QueryPropertyMap;
+ QueryPropertyMap query_properties_;
+
+ bool debugger_initial_suspension_;
+
+ // Used to block waiting for suspension
+ v8::base::Semaphore semaphore_;
+
+ mutable v8::base::Mutex mutex_;
+ //////////////////////////////////////////////////////////////////////////////
+ // Protected by {mutex_}:
+
+ // Current isolate. This is not null only when the target is in a Suspended
+ // state and it is the isolate associated to the current call stack and used
+ // for all debugging activities.
+ Isolate* current_isolate_;
+
+ // Call stack when the execution is suspended.
+ std::vector<wasm_addr_t> call_frames_;
+
+ // End of fields protected by {mutex_}.
+ //////////////////////////////////////////////////////////////////////////////
+
+ DISALLOW_COPY_AND_ASSIGN(Target);
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
diff --git a/src/debug/wasm/gdb-server/transport.cc b/src/debug/wasm/gdb-server/transport.cc
new file mode 100644
index 0000000..f1aed96
--- /dev/null
+++ b/src/debug/wasm/gdb-server/transport.cc
@@ -0,0 +1,457 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/transport.h"
+#include <fcntl.h>
+
+#ifndef SD_BOTH
+#define SD_BOTH 2
+#endif
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+SocketBinding::SocketBinding(SocketHandle socket_handle)
+ : socket_handle_(socket_handle) {}
+
+// static
+SocketBinding SocketBinding::Bind(uint16_t tcp_port) {
+ SocketHandle socket_handle = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (socket_handle == InvalidSocket) {
+ TRACE_GDB_REMOTE("Failed to create socket.\n");
+ return SocketBinding(InvalidSocket);
+ }
+ struct sockaddr_in sockaddr;
+ // Clearing sockaddr_in first appears to be necessary on Mac OS X.
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ socklen_t addrlen = static_cast<socklen_t>(sizeof(sockaddr));
+ sockaddr.sin_family = AF_INET;
+ sockaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sockaddr.sin_port = htons(tcp_port);
+
+#if _WIN32
+ // On Windows, SO_REUSEADDR has a different meaning than on POSIX systems.
+ // SO_REUSEADDR allows hijacking of an open socket by another process.
+ // The SO_EXCLUSIVEADDRUSE flag prevents this behavior.
+ // See:
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621(v=vs.85).aspx
+ //
+ // Additionally, unlike POSIX, TCP server sockets can be bound to
+ // ports in the TIME_WAIT state, without setting SO_REUSEADDR.
+ int exclusive_address = 1;
+ if (setsockopt(socket_handle, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+ reinterpret_cast<char*>(&exclusive_address),
+ sizeof(exclusive_address))) {
+ TRACE_GDB_REMOTE("Failed to set SO_EXCLUSIVEADDRUSE option.\n");
+ }
+#else
+ // On POSIX, this is necessary to ensure that the TCP port is released
+ // promptly when sel_ldr exits. Without this, the TCP port might
+ // only be released after a timeout, and later processes can fail
+ // to bind it.
+ int reuse_address = 1;
+ if (setsockopt(socket_handle, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<char*>(&reuse_address),
+ sizeof(reuse_address))) {
+ TRACE_GDB_REMOTE("Failed to set SO_REUSEADDR option.\n");
+ }
+#endif
+
+ if (bind(socket_handle, reinterpret_cast<struct sockaddr*>(&sockaddr),
+ addrlen)) {
+ TRACE_GDB_REMOTE("Failed to bind server.\n");
+ return SocketBinding(InvalidSocket);
+ }
+
+ if (listen(socket_handle, 1)) {
+ TRACE_GDB_REMOTE("Failed to listen.\n");
+ return SocketBinding(InvalidSocket);
+ }
+ return SocketBinding(socket_handle);
+}
+
+std::unique_ptr<SocketTransport> SocketBinding::CreateTransport() {
+ return std::make_unique<SocketTransport>(socket_handle_);
+}
+
+uint16_t SocketBinding::GetBoundPort() {
+ struct sockaddr_in saddr;
+ struct sockaddr* psaddr = reinterpret_cast<struct sockaddr*>(&saddr);
+ // Clearing sockaddr_in first appears to be necessary on Mac OS X.
+ memset(&saddr, 0, sizeof(saddr));
+ socklen_t addrlen = static_cast<socklen_t>(sizeof(saddr));
+ if (::getsockname(socket_handle_, psaddr, &addrlen)) {
+ TRACE_GDB_REMOTE("Failed to retrieve bound address.\n");
+ return 0;
+ }
+ return ntohs(saddr.sin_port);
+}
+
+// Do not delay sending small packets. This significantly speeds up
+// remote debugging. Debug stub uses buffering to send outgoing packets
+// so they are not split into more TCP packets than necessary.
+void DisableNagleAlgorithm(SocketHandle socket) {
+ int nodelay = 1;
+ if (::setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
+ reinterpret_cast<char*>(&nodelay), sizeof(nodelay))) {
+ TRACE_GDB_REMOTE("Failed to set TCP_NODELAY option.\n");
+ }
+}
+
+Transport::Transport(SocketHandle s)
+ : buf_(new char[kBufSize]),
+ pos_(0),
+ size_(0),
+ handle_bind_(s),
+ handle_accept_(InvalidSocket) {}
+
+Transport::~Transport() {
+ if (handle_accept_ != InvalidSocket) {
+ CloseSocket(handle_accept_);
+ }
+}
+
+void Transport::CopyFromBuffer(char** dst, int32_t* len) {
+ int32_t copy_bytes = std::min(*len, size_ - pos_);
+ memcpy(*dst, buf_.get() + pos_, copy_bytes);
+ pos_ += copy_bytes;
+ *len -= copy_bytes;
+ *dst += copy_bytes;
+}
+
+bool Transport::Read(char* dst, int32_t len) {
+ if (pos_ < size_) {
+ CopyFromBuffer(&dst, &len);
+ }
+ while (len > 0) {
+ pos_ = 0;
+ size_ = 0;
+ if (!ReadSomeData()) {
+ return false;
+ }
+ CopyFromBuffer(&dst, &len);
+ }
+ return true;
+}
+
+bool Transport::Write(const char* src, int32_t len) {
+ while (len > 0) {
+ ssize_t result = ::send(handle_accept_, src, len, 0);
+ if (result > 0) {
+ src += result;
+ len -= result;
+ continue;
+ }
+ if (result == 0) {
+ return false;
+ }
+ if (SocketGetLastError() != kErrInterrupt) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Return true if there is data to read.
+bool Transport::IsDataAvailable() const {
+ if (pos_ < size_) {
+ return true;
+ }
+ fd_set fds;
+
+ FD_ZERO(&fds);
+ FD_SET(handle_accept_, &fds);
+
+ // We want a "non-blocking" check
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+
+ // Check if this file handle can select on read
+ int cnt = select(static_cast<int>(handle_accept_) + 1, &fds, 0, 0, &timeout);
+
+ // If we are ready, or if there is an error. We return true
+ // on error, to let the next IO request fail.
+ if (cnt != 0) return true;
+
+ return false;
+}
+
+void Transport::Close() {
+ ::shutdown(handle_bind_, SD_BOTH);
+ CloseSocket(handle_bind_);
+ Disconnect();
+}
+
+void Transport::Disconnect() {
+ if (handle_accept_ != InvalidSocket) {
+ // Shutdown the connection in both directions. This should
+ // always succeed, and nothing we can do if this fails.
+ ::shutdown(handle_accept_, SD_BOTH);
+ CloseSocket(handle_accept_);
+ handle_accept_ = InvalidSocket;
+ }
+}
+
+#if _WIN32
+
+SocketTransport::SocketTransport(SocketHandle s) : Transport(s) {
+ socket_event_ = WSA_INVALID_EVENT;
+ faulted_thread_event_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (faulted_thread_event_ == NULL) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::SocketTransport: Failed to create event object for "
+ "faulted thread\n");
+ }
+}
+
+SocketTransport::~SocketTransport() {
+ if (!CloseHandle(faulted_thread_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "event\n");
+ }
+
+ if (socket_event_) {
+ if (!::WSACloseEvent(socket_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "socket event\n");
+ }
+ }
+}
+
+bool SocketTransport::AcceptConnection() {
+ CHECK(handle_accept_ == InvalidSocket);
+ handle_accept_ = ::accept(handle_bind_, NULL, 0);
+ if (handle_accept_ != InvalidSocket) {
+ DisableNagleAlgorithm(handle_accept_);
+
+ // Create socket event
+ socket_event_ = ::WSACreateEvent();
+ if (socket_event_ == WSA_INVALID_EVENT) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::AcceptConnection: Failed to create socket event\n");
+ }
+
+ // Listen for close events in order to handle them correctly.
+ // Additionally listen for read readiness as WSAEventSelect sets the socket
+ // to non-blocking mode.
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547(v=vs.85).aspx
+ if (::WSAEventSelect(handle_accept_, socket_event_, FD_CLOSE | FD_READ) ==
+ SOCKET_ERROR) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::AcceptConnection: Failed to bind event to "
+ "socket\n");
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SocketTransport::ReadSomeData() {
+ while (true) {
+ ssize_t result =
+ ::recv(handle_accept_, buf_.get() + size_, kBufSize - size_, 0);
+ if (result > 0) {
+ size_ += result;
+ return true;
+ }
+ if (result == 0) {
+ return false; // The connection was gracefully closed.
+ }
+ // WSAEventSelect sets socket to non-blocking mode. This is essential
+ // for socket event notification to work, there is no workaround.
+ // See remarks section at the page
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
+ if (SocketGetLastError() == WSAEWOULDBLOCK) {
+ if (::WaitForSingleObject(socket_event_, INFINITE) == WAIT_FAILED) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::ReadSomeData: Failed to wait on socket event\n");
+ }
+ if (!::ResetEvent(socket_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::ReadSomeData: Failed to reset socket event\n");
+ }
+ continue;
+ }
+
+ if (SocketGetLastError() != kErrInterrupt) {
+ return false;
+ }
+ }
+}
+
+void SocketTransport::WaitForDebugStubEvent() {
+ // Don't wait if we already have data to read.
+ bool wait = !(pos_ < size_);
+
+ HANDLE handles[2];
+ handles[0] = faulted_thread_event_;
+ handles[1] = socket_event_;
+ int count = size_ < kBufSize ? 2 : 1;
+ int result =
+ WaitForMultipleObjects(count, handles, FALSE, wait ? INFINITE : 0);
+ if (result == WAIT_OBJECT_0 + 1) {
+ if (!ResetEvent(socket_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Failed to reset socket "
+ "event\n");
+ }
+ return;
+ } else if (result == WAIT_OBJECT_0) {
+ if (!ResetEvent(faulted_thread_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Failed to reset event\n");
+ }
+ return;
+ } else if (result == WAIT_TIMEOUT) {
+ return;
+ }
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Wait for events failed\n");
+}
+
+bool SocketTransport::SignalThreadEvent() {
+ if (!SetEvent(faulted_thread_event_)) {
+ return false;
+ }
+ return true;
+}
+
+void SocketTransport::Disconnect() {
+ Transport::Disconnect();
+
+ if (socket_event_ != WSA_INVALID_EVENT && !::WSACloseEvent(socket_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "socket event\n");
+ }
+ socket_event_ = WSA_INVALID_EVENT;
+ SignalThreadEvent();
+}
+
+#else // _WIN32
+
+SocketTransport::SocketTransport(SocketHandle s) : Transport(s) {
+ int fds[2];
+#if defined(__linux__)
+ int ret = pipe2(fds, O_CLOEXEC);
+#else
+ int ret = pipe(fds);
+#endif
+ if (ret < 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::SocketTransport: Failed to allocate pipe for faulted "
+ "thread\n");
+ }
+ faulted_thread_fd_read_ = fds[0];
+ faulted_thread_fd_write_ = fds[1];
+}
+
+SocketTransport::~SocketTransport() {
+ if (close(faulted_thread_fd_read_) != 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "event\n");
+ }
+ if (close(faulted_thread_fd_write_) != 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "event\n");
+ }
+}
+
+bool SocketTransport::AcceptConnection() {
+ CHECK(handle_accept_ == InvalidSocket);
+ handle_accept_ = ::accept(handle_bind_, NULL, 0);
+ if (handle_accept_ != InvalidSocket) {
+ DisableNagleAlgorithm(handle_accept_);
+ return true;
+ }
+ return false;
+}
+
+bool SocketTransport::ReadSomeData() {
+ while (true) {
+ ssize_t result =
+ ::recv(handle_accept_, buf_.get() + size_, kBufSize - size_, 0);
+ if (result > 0) {
+ size_ += result;
+ return true;
+ }
+ if (result == 0) {
+ return false; // The connection was gracefully closed.
+ }
+ if (SocketGetLastError() != kErrInterrupt) {
+ return false;
+ }
+ }
+}
+
+void SocketTransport::WaitForDebugStubEvent() {
+ // Don't wait if we already have data to read.
+ bool wait = !(pos_ < size_);
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(faulted_thread_fd_read_, &fds);
+ int max_fd = faulted_thread_fd_read_;
+ if (size_ < kBufSize) {
+ FD_SET(handle_accept_, &fds);
+ max_fd = std::max(max_fd, handle_accept_);
+ }
+
+ int ret;
+ // We don't need sleep-polling on Linux now, so we set either zero or infinite
+ // timeout.
+ if (wait) {
+ ret = select(max_fd + 1, &fds, NULL, NULL, NULL);
+ } else {
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ ret = select(max_fd + 1, &fds, NULL, NULL, &timeout);
+ }
+ if (ret < 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Failed to wait for "
+ "debug stub event\n");
+ }
+
+ if (ret > 0) {
+ if (FD_ISSET(faulted_thread_fd_read_, &fds)) {
+ char buf[16];
+ if (read(faulted_thread_fd_read_, &buf, sizeof(buf)) < 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Failed to read from "
+ "debug stub event pipe fd\n");
+ }
+ }
+ if (FD_ISSET(handle_accept_, &fds)) ReadSomeData();
+ }
+}
+
+bool SocketTransport::SignalThreadEvent() {
+ // Notify the debug stub by marking the thread as faulted.
+ char buf = 0;
+ if (write(faulted_thread_fd_write_, &buf, sizeof(buf)) != sizeof(buf)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport:SignalThreadEvent: Can't send debug stub "
+ "event\n");
+ return false;
+ }
+ return true;
+}
+
+#endif // _WIN32
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#undef SD_BOTH
diff --git a/src/debug/wasm/gdb-server/transport.h b/src/debug/wasm/gdb-server/transport.h
new file mode 100644
index 0000000..42bf438
--- /dev/null
+++ b/src/debug/wasm/gdb-server/transport.h
@@ -0,0 +1,193 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
+#define V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
+
+#include <sstream>
+#include <vector>
+#include "src/base/macros.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+#if _WIN32
+#include <windows.h>
+#include <winsock2.h>
+
+typedef SOCKET SocketHandle;
+
+#define CloseSocket closesocket
+#define InvalidSocket INVALID_SOCKET
+#define SocketGetLastError() WSAGetLastError()
+static const int kErrInterrupt = WSAEINTR;
+typedef int ssize_t;
+typedef int socklen_t;
+
+#else // _WIN32
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string>
+
+typedef int SocketHandle;
+
+#define CloseSocket close
+#define InvalidSocket (-1)
+#define SocketGetLastError() errno
+static const int kErrInterrupt = EINTR;
+
+#endif // _WIN32
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class SocketTransport;
+
+// Acts as a factory for Transport objects bound to a specified TCP port.
+class SocketBinding {
+ public:
+ // Wrap existing socket handle.
+ explicit SocketBinding(SocketHandle socket_handle);
+
+ // Bind to the specified TCP port.
+ static SocketBinding Bind(uint16_t tcp_port);
+
+ bool IsValid() const { return socket_handle_ != InvalidSocket; }
+
+ // Create a transport object from this socket binding
+ std::unique_ptr<SocketTransport> CreateTransport();
+
+ // Get port the socket is bound to.
+ uint16_t GetBoundPort();
+
+ private:
+ SocketHandle socket_handle_;
+};
+
+class V8_EXPORT_PRIVATE TransportBase {
+ public:
+ virtual ~TransportBase() {}
+
+ // Waits for an incoming connection on the bound port.
+ virtual bool AcceptConnection() = 0;
+
+ // Read {len} bytes from this transport, possibly blocking until enough data
+ // is available.
+ // {dst} must point to a buffer large enough to contain {len} bytes.
+ // Returns true on success.
+ // Returns false if the connection is closed; in that case the {dst} may have
+ // been partially overwritten.
+ virtual bool Read(char* dst, int32_t len) = 0;
+
+ // Write {len} bytes to this transport.
+ // Return true on success, false if the connection is closed.
+ virtual bool Write(const char* src, int32_t len) = 0;
+
+ // Return true if there is data to read.
+ virtual bool IsDataAvailable() const = 0;
+
+ // If we are connected to a debugger, gracefully closes the connection.
+ // This should be called when a debugging session gets closed.
+ virtual void Disconnect() = 0;
+
+ // Shuts down this transport, gracefully closing the existing connection and
+ // also closing the listening socket. This should be called when the GDB stub
+ // shuts down, when the program terminates.
+ virtual void Close() = 0;
+
+ // Blocks waiting for one of these two events to occur:
+ // - A network event (a new packet arrives, or the connection is dropped),
+ // - A thread event is signaled (the execution stopped because of a trap or
+ // breakpoint).
+ virtual void WaitForDebugStubEvent() = 0;
+
+ // Signal that this transport should leave an alertable wait state because
+ // the execution of the debuggee was stopped because of a trap or breakpoint.
+ virtual bool SignalThreadEvent() = 0;
+};
+
+class Transport : public TransportBase {
+ public:
+ explicit Transport(SocketHandle s);
+ ~Transport() override;
+
+ // TransportBase
+ bool Read(char* dst, int32_t len) override;
+ bool Write(const char* src, int32_t len) override;
+ bool IsDataAvailable() const override;
+ void Disconnect() override;
+ void Close() override;
+
+ static const int kBufSize = 4096;
+
+ protected:
+ // Copy buffered data to *dst up to len bytes and update dst and len.
+ void CopyFromBuffer(char** dst, int32_t* len);
+
+ // Read available data from the socket. Return false on EOF or error.
+ virtual bool ReadSomeData() = 0;
+
+ std::unique_ptr<char[]> buf_;
+ int32_t pos_;
+ int32_t size_;
+ SocketHandle handle_bind_;
+ SocketHandle handle_accept_;
+};
+
+#if _WIN32
+
+class SocketTransport : public Transport {
+ public:
+ explicit SocketTransport(SocketHandle s);
+ ~SocketTransport() override;
+
+ // TransportBase
+ bool AcceptConnection() override;
+ void Disconnect() override;
+ void WaitForDebugStubEvent() override;
+ bool SignalThreadEvent() override;
+
+ private:
+ bool ReadSomeData() override;
+
+ HANDLE socket_event_;
+ HANDLE faulted_thread_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketTransport);
+};
+
+#else // _WIN32
+
+class SocketTransport : public Transport {
+ public:
+ explicit SocketTransport(SocketHandle s);
+ ~SocketTransport() override;
+
+ // TransportBase
+ bool AcceptConnection() override;
+ void WaitForDebugStubEvent() override;
+ bool SignalThreadEvent() override;
+
+ private:
+ bool ReadSomeData() override;
+
+ int faulted_thread_fd_read_;
+ int faulted_thread_fd_write_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketTransport);
+};
+
+#endif // _WIN32
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
diff --git a/src/debug/wasm/gdb-server/wasm-module-debug.cc b/src/debug/wasm/gdb-server/wasm-module-debug.cc
new file mode 100644
index 0000000..f0b77bc
--- /dev/null
+++ b/src/debug/wasm/gdb-server/wasm-module-debug.cc
@@ -0,0 +1,387 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/wasm-module-debug.h"
+
+#include "src/api/api-inl.h"
+#include "src/api/api.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/frames.h"
+#include "src/objects/script.h"
+#include "src/wasm/wasm-debug.h"
+#include "src/wasm/wasm-value.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+WasmModuleDebug::WasmModuleDebug(v8::Isolate* isolate,
+ Local<debug::WasmScript> wasm_script) {
+ DCHECK_EQ(Script::TYPE_WASM, Utils::OpenHandle(*wasm_script)->type());
+
+ isolate_ = isolate;
+ wasm_script_ = Global<debug::WasmScript>(isolate, wasm_script);
+}
+
+std::string WasmModuleDebug::GetModuleName() const {
+ v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+ v8::Local<v8::String> name;
+ std::string module_name;
+ if (wasm_script->Name().ToLocal(&name)) {
+ module_name = *(v8::String::Utf8Value(isolate_, name));
+ }
+ return module_name;
+}
+
+Handle<WasmInstanceObject> WasmModuleDebug::GetFirstWasmInstance() {
+ v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+ Handle<Script> script = Utils::OpenHandle(*wasm_script);
+
+ Handle<WeakArrayList> weak_instance_list(script->wasm_weak_instance_list(),
+ GetIsolate());
+ if (weak_instance_list->length() > 0) {
+ MaybeObject maybe_instance = weak_instance_list->Get(0);
+ if (maybe_instance->IsWeak()) {
+ Handle<WasmInstanceObject> instance(
+ WasmInstanceObject::cast(maybe_instance->GetHeapObjectAssumeWeak()),
+ GetIsolate());
+ return instance;
+ }
+ }
+ return Handle<WasmInstanceObject>::null();
+}
+
+int GetLEB128Size(Vector<const uint8_t> module_bytes, int offset) {
+ int index = offset;
+ while (module_bytes[index] & 0x80) index++;
+ return index + 1 - offset;
+}
+
+int ReturnPc(const NativeModule* native_module, int pc) {
+ Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
+ uint8_t opcode = wire_bytes[pc];
+ switch (opcode) {
+ case kExprCallFunction: {
+ // skip opcode
+ pc++;
+ // skip function index
+ return pc + GetLEB128Size(wire_bytes, pc);
+ }
+ case kExprCallIndirect: {
+ // skip opcode
+ pc++;
+ // skip signature index
+ pc += GetLEB128Size(wire_bytes, pc);
+ // skip table index
+ return pc + GetLEB128Size(wire_bytes, pc);
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+// static
+std::vector<wasm_addr_t> WasmModuleDebug::GetCallStack(
+ uint32_t debug_context_id, Isolate* isolate) {
+ std::vector<wasm_addr_t> call_stack;
+ for (StackFrameIterator frame_it(isolate); !frame_it.done();
+ frame_it.Advance()) {
+ StackFrame* const frame = frame_it.frame();
+ switch (frame->type()) {
+ case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
+ case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH:
+ case StackFrame::OPTIMIZED:
+ case StackFrame::INTERPRETED:
+ case StackFrame::BUILTIN:
+ case StackFrame::WASM: {
+ // A standard frame may include many summarized frames, due to inlining.
+ std::vector<FrameSummary> frames;
+ CommonFrame::cast(frame)->Summarize(&frames);
+ for (size_t i = frames.size(); i-- != 0;) {
+ int offset = 0;
+ Handle<Script> script;
+
+ auto& summary = frames[i];
+ if (summary.IsJavaScript()) {
+ FrameSummary::JavaScriptFrameSummary const& java_script =
+ summary.AsJavaScript();
+ offset = java_script.code_offset();
+ script = Handle<Script>::cast(java_script.script());
+ } else if (summary.IsWasm()) {
+ FrameSummary::WasmFrameSummary const& wasm = summary.AsWasm();
+ offset = GetWasmFunctionOffset(wasm.wasm_instance()->module(),
+ wasm.function_index()) +
+ wasm.byte_offset();
+ script = wasm.script();
+
+ bool zeroth_frame = call_stack.empty();
+ if (!zeroth_frame) {
+ const NativeModule* native_module =
+ wasm.wasm_instance()->module_object().native_module();
+ offset = ReturnPc(native_module, offset);
+ }
+ }
+
+ if (offset > 0) {
+ call_stack.push_back(
+ {debug_context_id << 16 | script->id(), uint32_t(offset)});
+ }
+ }
+ break;
+ }
+
+ case StackFrame::BUILTIN_EXIT:
+ default:
+ // ignore the frame.
+ break;
+ }
+ }
+ if (call_stack.empty()) call_stack.push_back({1, 0});
+ return call_stack;
+}
+
+// static
+std::vector<FrameSummary> WasmModuleDebug::FindWasmFrame(
+ StackTraceFrameIterator* frame_it, uint32_t* frame_index) {
+ while (!frame_it->done()) {
+ StackFrame* const frame = frame_it->frame();
+ switch (frame->type()) {
+ case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
+ case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH:
+ case StackFrame::OPTIMIZED:
+ case StackFrame::INTERPRETED:
+ case StackFrame::BUILTIN:
+ case StackFrame::WASM: {
+ // A standard frame may include many summarized frames, due to inlining.
+ std::vector<FrameSummary> frames;
+ CommonFrame::cast(frame)->Summarize(&frames);
+ const size_t frame_count = frames.size();
+ DCHECK_GT(frame_count, 0);
+
+ if (frame_count > *frame_index) {
+ if (frame_it->is_wasm())
+ return frames;
+ else
+ return {};
+ } else {
+ *frame_index -= frame_count;
+ frame_it->Advance();
+ }
+ break;
+ }
+
+ case StackFrame::BUILTIN_EXIT:
+ default:
+ // ignore the frame.
+ break;
+ }
+ }
+ return {};
+}
+
+// static
+Handle<WasmInstanceObject> WasmModuleDebug::GetWasmInstance(
+ Isolate* isolate, uint32_t frame_index) {
+ StackTraceFrameIterator frame_it(isolate);
+ std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+ if (frames.empty()) {
+ return Handle<WasmInstanceObject>::null();
+ }
+
+ int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+ const FrameSummary::WasmFrameSummary& summary =
+ frames[reversed_index].AsWasm();
+ return summary.wasm_instance();
+}
+
+// static
+bool WasmModuleDebug::GetWasmGlobal(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size) {
+ HandleScope handles(isolate);
+
+ Handle<WasmInstanceObject> instance = GetWasmInstance(isolate, frame_index);
+ if (!instance.is_null()) {
+ Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
+ const wasm::WasmModule* module = module_object->module();
+ if (index < module->globals.size()) {
+ wasm::WasmValue wasm_value =
+ WasmInstanceObject::GetGlobalValue(instance, module->globals[index]);
+ return GetWasmValue(wasm_value, buffer, buffer_size, size);
+ }
+ }
+ return false;
+}
+
+// static
+bool WasmModuleDebug::GetWasmLocal(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size) {
+ HandleScope handles(isolate);
+
+ StackTraceFrameIterator frame_it(isolate);
+ std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+ if (frames.empty()) {
+ return false;
+ }
+
+ int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+ const FrameSummary& summary = frames[reversed_index];
+ if (summary.IsWasm()) {
+ Handle<WasmInstanceObject> instance = summary.AsWasm().wasm_instance();
+ if (!instance.is_null()) {
+ Handle<WasmModuleObject> module_object(instance->module_object(),
+ isolate);
+ wasm::NativeModule* native_module = module_object->native_module();
+ DebugInfo* debug_info = native_module->GetDebugInfo();
+ if (static_cast<uint32_t>(debug_info->GetNumLocals(
+ isolate, frame_it.frame()->pc())) > index) {
+ wasm::WasmValue wasm_value = debug_info->GetLocalValue(
+ index, isolate, frame_it.frame()->pc(), frame_it.frame()->fp(),
+ frame_it.frame()->callee_fp());
+ return GetWasmValue(wasm_value, buffer, buffer_size, size);
+ }
+ }
+ }
+ return false;
+}
+
+// static
+bool WasmModuleDebug::GetWasmStackValue(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size) {
+ HandleScope handles(isolate);
+
+ StackTraceFrameIterator frame_it(isolate);
+ std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+ if (frames.empty()) {
+ return false;
+ }
+
+ int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+ const FrameSummary& summary = frames[reversed_index];
+ if (summary.IsWasm()) {
+ Handle<WasmInstanceObject> instance = summary.AsWasm().wasm_instance();
+ if (!instance.is_null()) {
+ Handle<WasmModuleObject> module_object(instance->module_object(),
+ isolate);
+ wasm::NativeModule* native_module = module_object->native_module();
+ DebugInfo* debug_info = native_module->GetDebugInfo();
+ if (static_cast<uint32_t>(debug_info->GetStackDepth(
+ isolate, frame_it.frame()->pc())) > index) {
+ WasmValue wasm_value = debug_info->GetStackValue(
+ index, isolate, frame_it.frame()->pc(), frame_it.frame()->fp(),
+ frame_it.frame()->callee_fp());
+ return GetWasmValue(wasm_value, buffer, buffer_size, size);
+ }
+ }
+ }
+ return false;
+}
+
+// static
+uint32_t WasmModuleDebug::GetWasmMemory(Isolate* isolate, uint32_t frame_index,
+ uint32_t offset, uint8_t* buffer,
+ uint32_t size) {
+ HandleScope handles(isolate);
+
+ uint32_t bytes_read = 0;
+ Handle<WasmInstanceObject> instance = GetWasmInstance(isolate, frame_index);
+ if (!instance.is_null()) {
+ uint8_t* mem_start = instance->memory_start();
+ size_t mem_size = instance->memory_size();
+ if (static_cast<uint64_t>(offset) + size <= mem_size) {
+ memcpy(buffer, mem_start + offset, size);
+ bytes_read = size;
+ } else if (offset < mem_size) {
+ bytes_read = static_cast<uint32_t>(mem_size) - offset;
+ memcpy(buffer, mem_start + offset, bytes_read);
+ }
+ }
+ return bytes_read;
+}
+
+uint32_t WasmModuleDebug::GetWasmModuleBytes(wasm_addr_t wasm_addr,
+ uint8_t* buffer, uint32_t size) {
+ uint32_t bytes_read = 0;
+ // Any instance will work.
+ Handle<WasmInstanceObject> instance = GetFirstWasmInstance();
+ if (!instance.is_null()) {
+ Handle<WasmModuleObject> module_object(instance->module_object(),
+ GetIsolate());
+ wasm::NativeModule* native_module = module_object->native_module();
+ const wasm::ModuleWireBytes wire_bytes(native_module->wire_bytes());
+ uint32_t offset = wasm_addr.Offset();
+ if (offset < wire_bytes.length()) {
+ uint32_t module_size = static_cast<uint32_t>(wire_bytes.length());
+ bytes_read = module_size - offset >= size ? size : module_size - offset;
+ memcpy(buffer, wire_bytes.start() + offset, bytes_read);
+ }
+ }
+ return bytes_read;
+}
+
+bool WasmModuleDebug::AddBreakpoint(uint32_t offset, int* breakpoint_id) {
+ v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+ Handle<Script> script = Utils::OpenHandle(*wasm_script);
+ Handle<String> condition = GetIsolate()->factory()->empty_string();
+ int breakpoint_address = static_cast<int>(offset);
+ return GetIsolate()->debug()->SetBreakPointForScript(
+ script, condition, &breakpoint_address, breakpoint_id);
+}
+
+void WasmModuleDebug::RemoveBreakpoint(uint32_t offset, int breakpoint_id) {
+ v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+ Handle<Script> script = Utils::OpenHandle(*wasm_script);
+ GetIsolate()->debug()->RemoveBreakpointForWasmScript(script, breakpoint_id);
+}
+
+void WasmModuleDebug::PrepareStep() {
+ i::Isolate* isolate = GetIsolate();
+ DebugScope debug_scope(isolate->debug());
+ debug::PrepareStep(reinterpret_cast<v8::Isolate*>(isolate),
+ debug::StepAction::StepIn);
+}
+
+template <typename T>
+bool StoreValue(const T& value, uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ *size = sizeof(value);
+ if (*size > buffer_size) return false;
+ memcpy(buffer, &value, *size);
+ return true;
+}
+
+// static
+bool WasmModuleDebug::GetWasmValue(const wasm::WasmValue& wasm_value,
+ uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ switch (wasm_value.type().kind()) {
+ case wasm::kWasmI32.kind():
+ return StoreValue(wasm_value.to_i32(), buffer, buffer_size, size);
+ case wasm::kWasmI64.kind():
+ return StoreValue(wasm_value.to_i64(), buffer, buffer_size, size);
+ case wasm::kWasmF32.kind():
+ return StoreValue(wasm_value.to_f32(), buffer, buffer_size, size);
+ case wasm::kWasmF64.kind():
+ return StoreValue(wasm_value.to_f64(), buffer, buffer_size, size);
+ case wasm::kWasmS128.kind():
+ return StoreValue(wasm_value.to_s128(), buffer, buffer_size, size);
+
+ case wasm::kWasmStmt.kind():
+ case wasm::kWasmExternRef.kind():
+ case wasm::kWasmFuncRef.kind():
+ case wasm::kWasmExnRef.kind():
+ case wasm::kWasmBottom.kind():
+ default:
+ // Not supported
+ return false;
+ }
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/wasm-module-debug.h b/src/debug/wasm/gdb-server/wasm-module-debug.h
new file mode 100644
index 0000000..10e6a5d
--- /dev/null
+++ b/src/debug/wasm/gdb-server/wasm-module-debug.h
@@ -0,0 +1,105 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_
+#define V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_
+
+#include "src/debug/debug.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+#include "src/execution/frames.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+
+class WasmValue;
+
+namespace gdb_server {
+
+// Represents the interface to access the Wasm engine state for a given module.
+// For the moment it only works with interpreted functions, in the future it
+// could be extended to also support Liftoff.
+class WasmModuleDebug {
+ public:
+ WasmModuleDebug(v8::Isolate* isolate, Local<debug::WasmScript> script);
+
+ std::string GetModuleName() const;
+ i::Isolate* GetIsolate() const {
+ return reinterpret_cast<i::Isolate*>(isolate_);
+ }
+
+ // Gets the value of the {index}th global value.
+ static bool GetWasmGlobal(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Gets the value of the {index}th local value in the {frame_index}th stack
+ // frame.
+ static bool GetWasmLocal(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Gets the value of the {index}th value in the operand stack.
+ static bool GetWasmStackValue(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Reads {size} bytes, starting from {offset}, from the Memory instance
+ // associated to this module.
+ // Returns the number of byte copied to {buffer}, or 0 is case of error.
+ // Note: only one Memory for Module is currently supported.
+ static uint32_t GetWasmMemory(Isolate* isolate, uint32_t frame_index,
+ uint32_t offset, uint8_t* buffer,
+ uint32_t size);
+
+ // Gets {size} bytes, starting from {offset}, from the Code space of this
+ // module.
+ // Returns the number of byte copied to {buffer}, or 0 is case of error.
+ uint32_t GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer,
+ uint32_t size);
+
+ // Inserts a breakpoint at the offset {offset} of this module.
+ // Returns {true} if the breakpoint was successfully added.
+ bool AddBreakpoint(uint32_t offset, int* breakpoint_id);
+
+ // Removes a breakpoint at the offset {offset} of the this module.
+ void RemoveBreakpoint(uint32_t offset, int breakpoint_id);
+
+ // Handle stepping in wasm functions via the wasm interpreter.
+ void PrepareStep();
+
+ // Returns the current stack trace as a vector of instruction pointers.
+ static std::vector<wasm_addr_t> GetCallStack(uint32_t debug_context_id,
+ Isolate* isolate);
+
+ private:
+ // Returns the module WasmInstance associated to the {frame_index}th frame
+ // in the call stack.
+ static Handle<WasmInstanceObject> GetWasmInstance(Isolate* isolate,
+ uint32_t frame_index);
+
+ // Returns its first WasmInstance for this Wasm module.
+ Handle<WasmInstanceObject> GetFirstWasmInstance();
+
+ // Iterates on current stack frames and return frame information for the
+ // {frame_index} specified.
+ // Returns an empty array if the frame specified does not correspond to a Wasm
+ // stack frame.
+ static std::vector<FrameSummary> FindWasmFrame(
+ StackTraceFrameIterator* frame_it, uint32_t* frame_index);
+
+ // Converts a WasmValue into an array of bytes.
+ static bool GetWasmValue(const wasm::WasmValue& wasm_value, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ v8::Isolate* isolate_;
+ Global<debug::WasmScript> wasm_script_;
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_