Commit 03fc4149 authored by Paolo Severini's avatar Paolo Severini Committed by Commit Bot

Add initial support for Wasm debugging with LLDB: implements a GDB-remote stub

This is the first piece of the wasm debugging prototype (besides the changes to
add/remove breakpoints in WasmModuleObject made with
https://chromium.googlesource.com/v8/v8.git/+/e699f39caed9a23f8e20bd3a0386a3236e272737).

This changelist adds the infrastructure for a GDB-remote stub that will be used
to manage debugging sessions via the gdb-remote protocol.
It enables the creation and termination of debugging sessions over TCP
connections that are managed in a separate thread.
The logic to actually send, receive and decode GDB-remote packets will be part
of a future changelist.

Build with: v8_enable_wasm_gdb_remote_debugging = true
Run with:
  --wasm-gdb-remote                  Enables Wasm debugging with LLDB
                                     (default: false)
  --wasm-gdb-remote-port             TCP port to be used for debugging
                                     (default: 8765)
  --wasm-pause-waiting-for-debugger  Pauses the execution of Wasm code waiting
                                     for a debugger (default: false)
  --trace-wasm-gdb-remote            Enables tracing of Gdb-remote packets
                                     (default: false)

Note that most of this code is "borrowed" from the code of the Chromium NaCL
GDB-remote stub (located in Chromium in src\native_client\src\trusted\debug_stub).

Implementation details:
- class GdbServer acts as a singleton manager for the gdb-remote stub. It is
  instantiated as soon as the first Wasm module is loaded in the Wasm engine.
- class GdbServerThread spawns the worker thread for the TCP connection.
- class Transport manages the socket connection, in a portable way.
- class Session represents a remote debugging session.
- class Target represents a debugging target and it’s the place where the
  debugging packets will be processed and will implement the logic to debug
  a Wasm engine.

Bug: chromium:1010467
Change-Id: Ib2324e5901f5ae1d855b96b99ef0995d407322b6
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1923407Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarMichael Achenbach <machenbach@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#66379}
parent 9bb73365
...@@ -517,6 +517,9 @@ config("features") { ...@@ -517,6 +517,9 @@ config("features") {
if (v8_control_flow_integrity) { if (v8_control_flow_integrity) {
defines += [ "V8_ENABLE_CONTROL_FLOW_INTEGRITY" ] defines += [ "V8_ENABLE_CONTROL_FLOW_INTEGRITY" ]
} }
if (v8_enable_wasm_gdb_remote_debugging) {
defines += [ "V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING" ]
}
} }
config("toolchain") { config("toolchain") {
...@@ -3049,6 +3052,22 @@ v8_source_set("v8_base_without_compiler") { ...@@ -3049,6 +3052,22 @@ v8_source_set("v8_base_without_compiler") {
sources += v8_third_party_heap_files sources += v8_third_party_heap_files
} }
if (v8_enable_wasm_gdb_remote_debugging) {
sources += [
"src/debug/wasm/gdb-server/gdb-server-thread.cc",
"src/debug/wasm/gdb-server/gdb-server-thread.h",
"src/debug/wasm/gdb-server/gdb-server.cc",
"src/debug/wasm/gdb-server/gdb-server.h",
"src/debug/wasm/gdb-server/session.cc",
"src/debug/wasm/gdb-server/session.h",
"src/debug/wasm/gdb-server/target.cc",
"src/debug/wasm/gdb-server/target.h",
"src/debug/wasm/gdb-server/transport.cc",
"src/debug/wasm/gdb-server/transport.h",
"src/debug/wasm/gdb-server/util.h",
]
}
if (v8_check_header_includes) { if (v8_check_header_includes) {
# This file will be generated by tools/generate-header-include-checks.py # This file will be generated by tools/generate-header-include-checks.py
# if the "check_v8_header_includes" gclient variable is set. # if the "check_v8_header_includes" gclient variable is set.
......
...@@ -60,6 +60,9 @@ declare_args() { ...@@ -60,6 +60,9 @@ declare_args() {
# Override global symbol level setting for v8 # Override global symbol level setting for v8
v8_symbol_level = symbol_level v8_symbol_level = symbol_level
# Enable WebAssembly debugging via GDB-remote protocol.
v8_enable_wasm_gdb_remote_debugging = false
} }
if (v8_use_external_startup_data == "") { if (v8_use_external_startup_data == "") {
......
// 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 true;
}
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
// 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();
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<Transport> 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_
// 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 "src/debug/wasm/gdb-server/gdb-server-thread.h"
#include "src/wasm/wasm-engine.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace gdb_server {
GdbServer::GdbServer() {
DCHECK(!thread_);
DCHECK(FLAG_wasm_gdb_remote);
thread_ = std::make_unique<GdbServerThread>(this);
// TODO(paolosev): does StartSynchronously hurt performances?
if (!thread_->StartAndInitialize()) {
TRACE_GDB_REMOTE(
"Cannot initialize thread, GDB-remote debugging will be disabled.\n");
thread_ = nullptr;
}
}
GdbServer::~GdbServer() {
if (thread_) {
thread_->Stop();
thread_->Join();
}
}
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
// 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 <memory>
#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace gdb_server {
// 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:
// Spawns a "GDB-remote" thread that will be used to communicate with the
// debugger. This should be called once, the first time a Wasm module is
// loaded in the Wasm engine.
GdbServer();
// Stops the "GDB-remote" thread and waits for it to complete. This should be
// called once, when the Wasm engine shuts down.
~GdbServer();
private:
std::unique_ptr<GdbServerThread> thread_;
DISALLOW_COPY_AND_ASSIGN(GdbServer);
};
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
// 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/transport.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace gdb_server {
Session::Session(Transport* transport) : io_(transport), connected_(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::GetPacket() {
char ch;
if (!GetChar(&ch)) return false;
// discard the input
return true;
}
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
// 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 Transport;
// Represents a gdb-remote debugging session.
class Session {
public:
explicit Session(Transport* transport);
// Attempt to receive a packet.
// For the moment this method is only used to check whether the TCP connection
// is still active; all bytes read are discarded.
bool GetPacket();
// Return true if there is data to read.
bool IsDataAvailable() const;
// Return true if the connection 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();
private:
bool GetChar(char* ch);
Transport* io_; // Transport object not owned by the Session.
bool connected_; // Is the connection still valid.
DISALLOW_COPY_AND_ASSIGN(Session);
};
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
// 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 "src/base/platform/time.h"
#include "src/debug/wasm/gdb-server/gdb-server.h"
#include "src/debug/wasm/gdb-server/session.h"
#include "src/debug/wasm/gdb-server/transport.h"
#include "src/debug/wasm/gdb-server/util.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace gdb_server {
Target::Target(GdbServer* gdb_server)
: status_(Status::Running), session_(nullptr) {}
void Target::Terminate() {
// Executed in the Isolate thread.
status_ = Status::Terminated;
}
void Target::Run(Session* session) {
// Executed in the GdbServer thread.
session_ = session;
do {
WaitForDebugEvent();
ProcessCommands();
} while (!IsTerminated() && session_->IsConnected());
session_ = nullptr;
}
void Target::WaitForDebugEvent() {
// Executed in the GdbServer thread.
if (status_ != Status::Terminated) {
// Wait for either:
// * the thread to fault (or single-step)
// * an interrupt from LLDB
session_->WaitForDebugStubEvent();
}
}
void Target::ProcessCommands() {
// GDB-remote messages are processed in the GDBServer thread.
if (IsTerminated()) {
return;
}
// TODO(paolosev)
// For the moment just discard any packet we receive from the debugger.
do {
if (!session_->GetPacket()) continue;
} while (session_->IsConnected());
}
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
// 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 "src/base/macros.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace gdb_server {
class GdbServer;
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; }
private:
// 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();
// Processes GDB-remote packets that arrive from the debugger.
// This method should be called when the debuggee has suspended its execution.
void ProcessCommands();
enum class Status { Running, Terminated };
std::atomic<Status> status_;
Session* session_; // Session object not owned by the Target.
DISALLOW_COPY_AND_ASSIGN(Target);
};
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
This diff is collapsed.
// 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/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 Transport;
// 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<Transport> CreateTransport();
// Get port the socket is bound to.
uint16_t GetBoundPort();
private:
SocketHandle socket_handle_;
};
class TransportBase {
public:
explicit TransportBase(SocketHandle s);
virtual ~TransportBase();
// 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.
bool Read(char* dst, int32_t len);
// Write {len} bytes to this transport.
// Return true on success, false if the connection is closed.
bool Write(const char* src, int32_t len);
// Return true if there is data to read.
bool IsDataAvailable() const;
// 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.
void Close();
// If a socket connection with a debugger is present, gracefully closes it.
// This should be called when a debugging session gets closed.
virtual void Disconnect();
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;
static const int kBufSize = 4096;
std::unique_ptr<char[]> buf_;
int32_t pos_;
int32_t size_;
SocketHandle handle_bind_;
SocketHandle handle_accept_;
};
#if _WIN32
class Transport : public TransportBase {
public:
explicit Transport(SocketHandle s);
~Transport() override;
// Waits for an incoming connection on the bound port.
bool AcceptConnection();
// 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).
void WaitForDebugStubEvent();
// Signal that this transport should leave an alertable wait state because
// the execution of the debuggee was stopped because of a trap or breakpoint.
bool SignalThreadEvent();
void Disconnect() override;
private:
bool ReadSomeData() override;
HANDLE socket_event_;
HANDLE faulted_thread_event_;
DISALLOW_COPY_AND_ASSIGN(Transport);
};
#else // _WIN32
class Transport : public TransportBase {
public:
explicit Transport(SocketHandle s);
~Transport() override;
// Waits for an incoming connection on the bound port.
bool AcceptConnection();
// Blocks waiting for one of these two events to occur:
// - A network event (a new packet arrives, or the connection is dropped),
// - The debuggee suspends execution because of a trap or breakpoint.
void WaitForDebugStubEvent();
// Signal that this transport should leave an alertable wait state because
// the execution of the debuggee was stopped because of a trap or breakpoint.
bool SignalThreadEvent();
private:
bool ReadSomeData() override;
int faulted_thread_fd_read_;
int faulted_thread_fd_write_;
DISALLOW_COPY_AND_ASSIGN(Transport);
};
#endif // _WIN32
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
// 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_UTIL_H_
#define V8_DEBUG_WASM_GDB_SERVER_UTIL_H_
#include <string>
#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)
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_DEBUG_WASM_GDB_SERVER_UTIL_H_
...@@ -1392,6 +1392,18 @@ DEFINE_BOOL(multi_mapped_mock_allocator, false, ...@@ -1392,6 +1392,18 @@ DEFINE_BOOL(multi_mapped_mock_allocator, false,
"Use a multi-mapped mock ArrayBuffer allocator for testing.") "Use a multi-mapped mock ArrayBuffer allocator for testing.")
#endif #endif
// Flags for Wasm GDB remote debugging.
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
#define DEFAULT_WASM_GDB_REMOTE_PORT 8765
DEFINE_BOOL(wasm_gdb_remote, false,
"enable GDB-remote for WebAssembly debugging")
DEFINE_INT(wasm_gdb_remote_port, DEFAULT_WASM_GDB_REMOTE_PORT,
"default port for WebAssembly debugging with LLDB.")
DEFINE_BOOL(wasm_pause_waiting_for_debugger, false,
"pause at the first Webassembly instruction waiting for a debugger "
"to attach")
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
// //
// GDB JIT integration flags. // GDB JIT integration flags.
// //
...@@ -1477,6 +1489,10 @@ DEFINE_BOOL(print_break_location, false, "print source location on debug break") ...@@ -1477,6 +1489,10 @@ DEFINE_BOOL(print_break_location, false, "print source location on debug break")
DEFINE_DEBUG_BOOL(trace_wasm_instances, false, DEFINE_DEBUG_BOOL(trace_wasm_instances, false,
"trace creation and collection of wasm instances") "trace creation and collection of wasm instances")
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
DEFINE_BOOL(trace_wasm_gdb_remote, false, "trace Webassembly GDB-remote server")
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
// //
// Logging and profiling flags // Logging and profiling flags
// //
......
...@@ -23,6 +23,10 @@ ...@@ -23,6 +23,10 @@
#include "src/wasm/streaming-decoder.h" #include "src/wasm/streaming-decoder.h"
#include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-objects-inl.h"
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
#include "src/debug/wasm/gdb-server/gdb-server.h"
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
namespace v8 { namespace v8 {
namespace internal { namespace internal {
namespace wasm { namespace wasm {
...@@ -358,6 +362,11 @@ struct WasmEngine::NativeModuleInfo { ...@@ -358,6 +362,11 @@ struct WasmEngine::NativeModuleInfo {
WasmEngine::WasmEngine() : code_manager_(FLAG_wasm_max_code_space * MB) {} WasmEngine::WasmEngine() : code_manager_(FLAG_wasm_max_code_space * MB) {}
WasmEngine::~WasmEngine() { WasmEngine::~WasmEngine() {
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
// Synchronize on the GDB-remote thread, if running.
gdb_server_ = nullptr;
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
// Synchronize on all background compile tasks. // Synchronize on all background compile tasks.
background_compile_task_manager_.CancelAndWait(); background_compile_task_manager_.CancelAndWait();
// All AsyncCompileJobs have been canceled. // All AsyncCompileJobs have been canceled.
...@@ -838,6 +847,12 @@ void WasmEngine::LogOutstandingCodesForIsolate(Isolate* isolate) { ...@@ -838,6 +847,12 @@ void WasmEngine::LogOutstandingCodesForIsolate(Isolate* isolate) {
std::shared_ptr<NativeModule> WasmEngine::NewNativeModule( std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
Isolate* isolate, const WasmFeatures& enabled, Isolate* isolate, const WasmFeatures& enabled,
std::shared_ptr<const WasmModule> module, size_t code_size_estimate) { std::shared_ptr<const WasmModule> module, size_t code_size_estimate) {
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
if (FLAG_wasm_gdb_remote && !gdb_server_) {
gdb_server_ = std::make_unique<gdb_server::GdbServer>();
}
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
std::shared_ptr<NativeModule> native_module = code_manager_.NewNativeModule( std::shared_ptr<NativeModule> native_module = code_manager_.NewNativeModule(
this, isolate, enabled, code_size_estimate, std::move(module)); this, isolate, enabled, code_size_estimate, std::move(module));
base::MutexGuard lock(&mutex_); base::MutexGuard lock(&mutex_);
......
...@@ -36,6 +36,10 @@ class ErrorThrower; ...@@ -36,6 +36,10 @@ class ErrorThrower;
struct ModuleWireBytes; struct ModuleWireBytes;
class WasmFeatures; class WasmFeatures;
namespace gdb_server {
class GdbServer;
}
class V8_EXPORT_PRIVATE CompilationResultResolver { class V8_EXPORT_PRIVATE CompilationResultResolver {
public: public:
virtual void OnCompilationSucceeded(Handle<WasmModuleObject> result) = 0; virtual void OnCompilationSucceeded(Handle<WasmModuleObject> result) = 0;
...@@ -367,6 +371,11 @@ class V8_EXPORT_PRIVATE WasmEngine { ...@@ -367,6 +371,11 @@ class V8_EXPORT_PRIVATE WasmEngine {
// engine, they must all be finished because they access the allocator. // engine, they must all be finished because they access the allocator.
CancelableTaskManager background_compile_task_manager_; CancelableTaskManager background_compile_task_manager_;
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
// Implements a GDB-remote stub for WebAssembly debugging.
std::unique_ptr<gdb_server::GdbServer> gdb_server_;
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
// This mutex protects all information which is mutated concurrently or // This mutex protects all information which is mutated concurrently or
// fields that are initialized lazily on the first access. // fields that are initialized lazily on the first access.
base::Mutex mutex_; base::Mutex mutex_;
......
# Copyright 2019 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.
[]
# Copyright 2019 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.
import os
import re
import shlex
import sys
from testrunner.local import testsuite
from testrunner.local import utils
from testrunner.objects import testcase
from testrunner.outproc import message
PY_FLAGS_PATTERN = re.compile(r"#\s+Flags:(.*)")
class PYTestCase(testcase.TestCase):
def get_shell(self):
return os.path.splitext(sys.executable)[0]
def get_command(self):
return super(PYTestCase, self).get_command()
def _get_cmd_params(self):
return self._get_files_params() + ['--', os.path.join(self._test_config.shell_dir, 'd8')] + self._get_source_flags()
def _get_shell_flags(self):
return []
class TestCase(PYTestCase):
def __init__(self, *args, **kwargs):
super(TestCase, self).__init__(*args, **kwargs)
source = self.get_source()
self._source_files = self._parse_source_files(source)
self._source_flags = self._parse_source_flags(source)
def _parse_source_files(self, source):
files = []
files.append(self._get_source_path())
return files
def _parse_source_flags(self, source=None):
source = source or self.get_source()
flags = []
for match in re.findall(PY_FLAGS_PATTERN, source):
flags += shlex.split(match.strip())
return flags
def _expected_fail(self):
path = self.path
while path:
head, tail = os.path.split(path)
if tail == 'fail':
return True
path = head
return False
def _get_files_params(self):
return self._source_files
def _get_source_flags(self):
return self._source_flags
def _get_source_path(self):
base_path = os.path.join(self.suite.root, self.path)
if os.path.exists(base_path + self._get_suffix()):
return base_path + self._get_suffix()
return base_path + '.py'
def skip_predictable(self):
return super(TestCase, self).skip_predictable() or self._expected_fail()
class PYTestLoader(testsuite.GenericTestLoader):
@property
def excluded_files(self):
return {'gdb_rsp.py', 'testcfg.py', '__init__.py'}
@property
def extensions(self):
return ['.py']
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return PYTestLoader
def _test_class(self):
return TestCase
def GetSuite(*args, **kwargs):
return TestSuite(*args, **kwargs)
# Copyright 2019 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.
# Flags: -expose-wasm --wasm_gdb_remote --wasm-pause-waiting-for-debugger --wasm-interpret-all test/debugging/wasm/gdb-server/test_files/test.js
import os
import subprocess
import unittest
import sys
import gdb_rsp
# These are set up by Main().
COMMAND = None
class Tests(unittest.TestCase):
def test_disconnect(self):
process = gdb_rsp.PopenDebugStub(COMMAND)
try:
# Connect and record the instruction pointer.
connection = gdb_rsp.GdbRspConnection()
connection.Close()
# Reconnect 3 times.
for _ in range(3):
connection = gdb_rsp.GdbRspConnection()
connection.Close()
finally:
gdb_rsp.KillProcess(process)
def Main():
index = sys.argv.index('--')
args = sys.argv[index + 1:]
# The remaining arguments go to unittest.main().
global COMMAND
COMMAND = args
unittest.main(argv=sys.argv[:index])
if __name__ == '__main__':
Main()
# Copyright 2019 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.
import socket
import subprocess
import time
SOCKET_ADDR = ('localhost', 8765)
def EnsurePortIsAvailable(addr=SOCKET_ADDR):
# As a sanity check, check that the TCP port is available by binding to it
# ourselves (and then unbinding). Otherwise, we could end up talking to an
# old instance of the GDB stub that is still hanging around, or to some
# unrelated service that uses the same port number. Of course, there is still
# a race condition because an unrelated process could bind the port after we
# unbind.
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
sock.bind(addr)
sock.close()
class GdbRspConnection(object):
def __init__(self, addr=SOCKET_ADDR):
self._socket = self._Connect(addr)
def _Connect(self, addr):
# We have to poll because we do not know when the GDB stub has successfully
# done bind() on the TCP port. This is inherently unreliable.
timeout_in_seconds = 10
poll_time_in_seconds = 0.1
for i in xrange(int(timeout_in_seconds / poll_time_in_seconds)):
# On Mac OS X, we have to create a new socket FD for each retry.
sock = socket.socket()
# Do not delay sending small packets. This significantly speeds up debug
# stub tests.
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
try:
sock.connect(addr)
except socket.error:
# Retry after a delay.
time.sleep(poll_time_in_seconds)
else:
return sock
raise Exception('Could not connect to the debug stub in %i seconds'
% timeout_in_seconds)
def Close(self):
self._socket.close()
def PopenDebugStub(command):
EnsurePortIsAvailable()
return subprocess.Popen(command)
def KillProcess(process):
if process.returncode is not None:
# kill() won't work if we've already wait()'ed on the process.
return
try:
process.kill()
except OSError:
if sys.platform == 'win32':
# If process is already terminated, kill() throws
# "WindowsError: [Error 5] Access is denied" on Windows.
pass
else:
raise
process.wait()
// Copyright 2019 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.
load("test/mjsunit/wasm/wasm-module-builder.js");
var builder = new WasmModuleBuilder();
builder.addFunction('mul', kSig_i_ii)
// input is 2 args of type int and output is int
.addBody([
kExprLocalGet, 0, // local.get i0
kExprLocalGet, 1, // local.get i1
kExprI32Mul]) // i32.sub i0 i1
.exportFunc();
const instance = builder.instantiate();
const wasm_f = instance.exports.mul;
function f() {
var result = wasm_f(21, 2);
return result;
}
try {
let val = 0;
while (true) {
val += f();
}
}
catch (e) {
print('*exception:* ' + e);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment