Commit 74e93186 authored by Paolo Severini's avatar Paolo Severini Committed by Commit Bot

Wasm debugging with LLDB: access Wasm engine state

This changelist makes the GDB-stub actually execute GDB-remote commands, by
accessing the Wasm engine state. More precisely:
- class GdbServer registers DebugDelegates that receive debug notifications when
  a new Wasm module is loaded, when execution suspends at a breakpoint or for an
  unhandled exception.
- Since the GDB-remote commands arrive on a separate thread, all
  queries from the debugger are transformed into Task objects, that are posted
  into a TaskRunner that runs in the Isolate thread.
- class WasmModuleDebug contains the logic to retrieve the value of globals, locals, memory ranges from the
  Wasm engine and to add/remove breakpoints.

Build with: v8_enable_wasm_gdb_remote_debugging = true
Run with: --wasm-gdb-remote
Test with: python tools\run-tests.py --outdir=out\debug_x64 debugging -j 1

Bug: chromium:1010467
Change-Id: I9703894620a027d3c920926db92e2ff809d84ab8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1941139Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#67412}
parent 97a4b795
...@@ -3150,6 +3150,8 @@ v8_source_set("v8_base_without_compiler") { ...@@ -3150,6 +3150,8 @@ v8_source_set("v8_base_without_compiler") {
"src/debug/wasm/gdb-server/target.h", "src/debug/wasm/gdb-server/target.h",
"src/debug/wasm/gdb-server/transport.cc", "src/debug/wasm/gdb-server/transport.cc",
"src/debug/wasm/gdb-server/transport.h", "src/debug/wasm/gdb-server/transport.h",
"src/debug/wasm/gdb-server/wasm-module-debug.cc",
"src/debug/wasm/gdb-server/wasm-module-debug.h",
] ]
} }
......
...@@ -73,7 +73,7 @@ std::vector<std::string> StringSplit(const string& instr, const char* delim) { ...@@ -73,7 +73,7 @@ std::vector<std::string> StringSplit(const string& instr, const char* delim) {
// If we still have something to process // If we still have something to process
if (*in) { if (*in) {
const char* start = in; const char* start = in;
int len = 0; size_t len = 0;
// Keep moving forward for all valid chars // Keep moving forward for all valid chars
while (*in && (strchr(delim, *in) == nullptr)) { while (*in && (strchr(delim, *in) == nullptr)) {
len++; len++;
......
...@@ -31,21 +31,20 @@ bool HexToUInt8(const char chars[2], uint8_t* byte); ...@@ -31,21 +31,20 @@ bool HexToUInt8(const char chars[2], uint8_t* byte);
// input char is unexpected. // input char is unexpected.
bool NibbleToUInt8(char ch, uint8_t* byte); 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 // Convert the memory pointed to by {mem} into a hex string in GDB-remote
// format. // format.
std::string Mem2Hex(const uint8_t* mem, size_t count); std::string Mem2Hex(const uint8_t* mem, size_t count);
std::string Mem2Hex(const std::string& str); std::string Mem2Hex(const std::string& str);
std::vector<std::string> V8_EXPORT_PRIVATE StringSplit(const std::string& instr,
const char* delim);
// For LLDB debugging, an address in a Wasm module code space is represented // 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: // with 64 bits, where the first 32 bits identify the module id:
// // +--------------------+--------------------+
// 63 32 0 // | module_id | offset |
// +---------------+---------------+ // +--------------------+--------------------+
// | module_id | offset | // <----- 32 bit -----> <----- 32 bit ----->
// +---------------+---------------+
class wasm_addr_t { class wasm_addr_t {
public: public:
wasm_addr_t(uint32_t module_id, uint32_t offset) wasm_addr_t(uint32_t module_id, uint32_t offset)
......
...@@ -34,6 +34,8 @@ class GdbServerThread : public v8::base::Thread { ...@@ -34,6 +34,8 @@ class GdbServerThread : public v8::base::Thread {
// closes any active debugging session. // closes any active debugging session.
void Stop(); void Stop();
Target& GetTarget() { return *target_; }
private: private:
void CleanupThread(); void CleanupThread();
......
This diff is collapsed.
...@@ -5,14 +5,18 @@ ...@@ -5,14 +5,18 @@
#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_ #ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_ #define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
#include <map>
#include <memory> #include <memory>
#include "src/debug/wasm/gdb-server/gdb-server-thread.h" #include "src/debug/wasm/gdb-server/gdb-server-thread.h"
#include "src/debug/wasm/gdb-server/wasm-module-debug.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
namespace wasm { namespace wasm {
namespace gdb_server { namespace gdb_server {
class TaskRunner;
// class GdbServer acts as a manager for the GDB-remote stub. It is instantiated // 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 // 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. // separate thread to accept connections and exchange messages with a debugger.
...@@ -37,26 +41,35 @@ class GdbServer { ...@@ -37,26 +41,35 @@ class GdbServer {
uint32_t module_id; uint32_t module_id;
std::string module_name; std::string module_name;
}; };
std::vector<WasmModuleInfo> GetLoadedModules() const; std::vector<WasmModuleInfo> GetLoadedModules();
// Queries the value of the {index} global value in the Wasm module identified // Queries the value of the {index} global value in the Wasm module identified
// by {wasm_module_id}. // by {frame_index}.
bool GetWasmGlobal(uint32_t wasm_module_id, uint32_t index, uint64_t* value); //
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 // Queries the value of the {index} local value in the {frame_index}th stack
// frame in the Wasm module identified by {wasm_module_id}. // frame in the Wasm module identified by {frame_index}.
bool GetWasmLocal(uint32_t wasm_module_id, uint32_t frame_index, //
uint32_t index, uint64_t* value); 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 // Reads {size} bytes, starting from {offset}, from the Memory instance
// associated to the Wasm module identified by {wasm_module_id}. // associated to the Wasm module identified by {frame_index}.
// Returns the number of bytes copied to {buffer}, or 0 is case of error. // Returns the number of bytes copied to {buffer}, or 0 is case of error.
// Note: only one Memory for Module is currently supported. // Note: only one Memory for Module is currently supported.
uint32_t GetWasmMemory(uint32_t wasm_module_id, uint32_t offset, //
uint8_t* buffer, uint32_t size); uint32_t GetWasmMemory(uint32_t frame_index, uint32_t offset, uint8_t* buffer,
uint32_t size);
// Reads {size} bytes, starting from {address}, from the Code space of the // Reads {size} bytes, starting from the low dword of {address}, from the Code
// Wasm module identified by {wasm_module_id}. // 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. // 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 GetWasmModuleBytes(wasm_addr_t address, uint8_t* buffer,
uint32_t size); uint32_t size);
...@@ -64,21 +77,119 @@ class GdbServer { ...@@ -64,21 +77,119 @@ class GdbServer {
// Inserts a breakpoint at the offset {offset} of the Wasm module identified // Inserts a breakpoint at the offset {offset} of the Wasm module identified
// by {wasm_module_id}. // by {wasm_module_id}.
// Returns true if the breakpoint was successfully added. // Returns true if the breakpoint was successfully added.
bool AddBreakpoint(uint32_t module_id, uint32_t offset); bool AddBreakpoint(uint32_t wasm_module_id, uint32_t offset);
// Removes a breakpoint at the offset {offset} of the Wasm module identified // Removes a breakpoint at the offset {offset} of the Wasm module identified
// by {wasm_module_id}. // by {wasm_module_id}.
// Returns true if the breakpoint was successfully removed. // Returns true if the breakpoint was successfully removed.
bool RemoveBreakpoint(uint32_t module_id, uint32_t offset); bool RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset);
// Returns the current call stack as a vector of program counters. // Returns the current call stack as a vector of program counters.
std::vector<wasm_addr_t> GetWasmCallStack() const; 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: private:
GdbServer() {} 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_; 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); DISALLOW_COPY_AND_ASSIGN(GdbServer);
}; };
......
...@@ -36,18 +36,20 @@ bool Session::GetChar(char* ch) { ...@@ -36,18 +36,20 @@ bool Session::GetChar(char* ch) {
return true; return true;
} }
bool Session::SendPacket(Packet* pkt) { bool Session::SendPacket(Packet* pkt, bool expect_ack) {
char ch; char ch;
do { do {
std::string data = pkt->GetPacketData(); std::string data = pkt->GetPacketData();
TRACE_GDB_REMOTE("TX %s\n", data.c_str()); 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()))) { if (!io_->Write(data.data(), static_cast<int32_t>(data.length()))) {
return false; return false;
} }
// If ACKs are off, we are done. // If ACKs are off, we are done.
if (!ack_enabled_) { if (!expect_ack || !ack_enabled_) {
break; break;
} }
......
...@@ -21,7 +21,7 @@ class V8_EXPORT_PRIVATE Session { ...@@ -21,7 +21,7 @@ class V8_EXPORT_PRIVATE Session {
explicit Session(TransportBase* transport); explicit Session(TransportBase* transport);
// Attempt to send a packet and optionally wait for an ACK from the receiver. // Attempt to send a packet and optionally wait for an ACK from the receiver.
bool SendPacket(Packet* packet); bool SendPacket(Packet* packet, bool expect_ack = true);
// Attempt to receive a packet. // Attempt to receive a packet.
bool GetPacket(Packet* packet); bool GetPacket(Packet* packet);
......
This diff is collapsed.
...@@ -33,7 +33,23 @@ class Target { ...@@ -33,7 +33,23 @@ class Target {
void Terminate(); void Terminate();
bool IsTerminated() const { return status_ == Status::Terminated; } 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: 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 // Initializes a map used to make fast lookups when handling query packets
// that have a constant response. // that have a constant response.
void InitQueryPropertyMap(); void InitQueryPropertyMap();
...@@ -43,11 +59,15 @@ class Target { ...@@ -43,11 +59,15 @@ class Target {
// closed; // closed;
// - The debuggee suspends execution because of a trap or breakpoint. // - The debuggee suspends execution because of a trap or breakpoint.
void WaitForDebugEvent(); void WaitForDebugEvent();
void ProcessDebugEvent();
// Processes GDB-remote packets that arrive from the debugger. // Processes GDB-remote packets that arrive from the debugger.
// This method should be called when the debuggee has suspended its execution. // This method should be called when the debuggee has suspended its execution.
void ProcessCommands(); 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 ErrorCode { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 };
enum class ProcessPacketResult { enum class ProcessPacketResult {
...@@ -69,22 +89,46 @@ class Target { ...@@ -69,22 +89,46 @@ class Target {
// (continue), 's' (step) and '?' (query halt reason) commands. // (continue), 's' (step) and '?' (query halt reason) commands.
void SetStopReply(Packet* pkt_out) const; void SetStopReply(Packet* pkt_out) const;
wasm_addr_t GetCurrentPc() 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_; GdbServer* gdb_server_;
enum class Status { Running, Terminated };
std::atomic<Status> status_; std::atomic<Status> status_;
// Signal being processed. // Signal being processed.
int8_t cur_signal_; std::atomic<int8_t> cur_signal_;
Session* session_; // Session object not owned by the Target. // Session object not owned by the Target.
Session* session_;
// Map used to make fast lookups when handling query packets. // Map used to make fast lookups when handling query packets.
typedef std::map<std::string, std::string> QueryPropertyMap; typedef std::map<std::string, std::string> QueryPropertyMap;
QueryPropertyMap query_properties_; 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); DISALLOW_COPY_AND_ASSIGN(Target);
}; };
......
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_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_
...@@ -1435,6 +1435,7 @@ DEFINE_BOOL(multi_mapped_mock_allocator, false, ...@@ -1435,6 +1435,7 @@ DEFINE_BOOL(multi_mapped_mock_allocator, false,
#define DEFAULT_WASM_GDB_REMOTE_PORT 8765 #define DEFAULT_WASM_GDB_REMOTE_PORT 8765
DEFINE_BOOL(wasm_gdb_remote, false, DEFINE_BOOL(wasm_gdb_remote, false,
"enable GDB-remote for WebAssembly debugging") "enable GDB-remote for WebAssembly debugging")
DEFINE_NEG_IMPLICATION(wasm_gdb_remote, wasm_tier_up)
DEFINE_INT(wasm_gdb_remote_port, DEFAULT_WASM_GDB_REMOTE_PORT, DEFINE_INT(wasm_gdb_remote_port, DEFAULT_WASM_GDB_REMOTE_PORT,
"default port for WebAssembly debugging with LLDB.") "default port for WebAssembly debugging with LLDB.")
DEFINE_BOOL(wasm_pause_waiting_for_debugger, false, DEFINE_BOOL(wasm_pause_waiting_for_debugger, false,
......
...@@ -868,9 +868,20 @@ void WasmEngine::AddIsolate(Isolate* isolate) { ...@@ -868,9 +868,20 @@ void WasmEngine::AddIsolate(Isolate* isolate) {
}; };
isolate->heap()->AddGCEpilogueCallback(callback, v8::kGCTypeMarkSweepCompact, isolate->heap()->AddGCEpilogueCallback(callback, v8::kGCTypeMarkSweepCompact,
nullptr); nullptr);
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
if (gdb_server_) {
gdb_server_->AddIsolate(isolate);
}
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
} }
void WasmEngine::RemoveIsolate(Isolate* isolate) { void WasmEngine::RemoveIsolate(Isolate* isolate) {
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
if (gdb_server_) {
gdb_server_->RemoveIsolate(isolate);
}
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
base::MutexGuard guard(&mutex_); base::MutexGuard guard(&mutex_);
auto it = isolates_.find(isolate); auto it = isolates_.find(isolate);
DCHECK_NE(isolates_.end(), it); DCHECK_NE(isolates_.end(), it);
...@@ -956,6 +967,7 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule( ...@@ -956,6 +967,7 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING #ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
if (FLAG_wasm_gdb_remote && !gdb_server_) { if (FLAG_wasm_gdb_remote && !gdb_server_) {
gdb_server_ = gdb_server::GdbServer::Create(); gdb_server_ = gdb_server::GdbServer::Create();
gdb_server_->AddIsolate(isolate);
} }
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING #endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
......
...@@ -79,7 +79,8 @@ class PYTestLoader(testsuite.GenericTestLoader): ...@@ -79,7 +79,8 @@ class PYTestLoader(testsuite.GenericTestLoader):
@property @property
def excluded_files(self): def excluded_files(self):
return {'gdb_rsp.py', 'testcfg.py', '__init__.py'} return {'gdb_rsp.py', 'testcfg.py', '__init__.py', 'test_basic.py',
'test_float.py', 'test_memory.py', 'test_trap.py'}
@property @property
def extensions(self): def extensions(self):
......
paolosev@microsoft.com
# COMPONENT: Blink>JavaScript>WebAssembly
\ No newline at end of file
# 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.
# Flags: -expose-wasm --wasm-gdb-remote --wasm-pause-waiting-for-debugger test/debugging/wasm/gdb-server/test_files/test_basic.js
import sys
import unittest
import gdb_rsp
import test_files.test_basic as test_basic
class Tests(unittest.TestCase):
def test_initial_breakpoint(self):
# Testing that the debuggee suspends when the debugger attaches.
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
reply = connection.RspRequest('?')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
def test_setting_removing_breakpoint(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
func_addr = module_load_addr + test_basic.BREAK_ADDRESS_1
# Set a breakpoint.
reply = connection.RspRequest('Z0,%x,1' % func_addr)
self.assertEqual(reply, 'OK')
# When we run the program, we should hit the breakpoint.
reply = connection.RspRequest('c')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
gdb_rsp.CheckInstructionPtr(connection, func_addr)
# Check that we can remove the breakpoint.
reply = connection.RspRequest('z0,%x,0' % func_addr)
self.assertEqual(reply, 'OK')
# Requesting removing a breakpoint on an address that does not
# have one should return an error.
reply = connection.RspRequest('z0,%x,0' % func_addr)
self.assertEqual(reply, 'E03')
def test_setting_breakpoint_on_invalid_address(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
# Requesting a breakpoint on an invalid address should give an error.
reply = connection.RspRequest('Z0,%x,1' % (1 << 32))
self.assertEqual(reply, 'E03')
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. # Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # 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 # Flags: -expose-wasm --wasm-gdb-remote --wasm-pause-waiting-for-debugger test/debugging/wasm/gdb-server/test_files/test_basic.js
from ctypes import *
import os import os
import subprocess import subprocess
import unittest import unittest
import sys import sys
import gdb_rsp import gdb_rsp
import test_files.test_basic as test_basic
# These are set up by Main(). # These are set up by Main().
COMMAND = None COMMAND = None
class Tests(unittest.TestCase): class Tests(unittest.TestCase):
def test_disconnect(self): def test_disconnect(self):
process = gdb_rsp.PopenDebugStub(COMMAND) process = gdb_rsp.PopenDebugStub(COMMAND)
try: try:
# Connect and record the instruction pointer. # Connect.
connection = gdb_rsp.GdbRspConnection() connection = gdb_rsp.GdbRspConnection()
connection.Close() connection.Close()
# Reconnect 3 times. # Reconnect 3 times.
...@@ -28,6 +31,29 @@ class Tests(unittest.TestCase): ...@@ -28,6 +31,29 @@ class Tests(unittest.TestCase):
finally: finally:
gdb_rsp.KillProcess(process) gdb_rsp.KillProcess(process)
def test_kill(self):
process = gdb_rsp.PopenDebugStub(COMMAND)
try:
connection = gdb_rsp.GdbRspConnection()
# Request killing the target.
reply = connection.RspRequest('k')
self.assertEqual(reply, 'OK')
signal = c_byte(process.wait()).value
self.assertEqual(signal, gdb_rsp.RETURNCODE_KILL)
finally:
gdb_rsp.KillProcess(process)
def test_detach(self):
process = gdb_rsp.PopenDebugStub(COMMAND)
try:
connection = gdb_rsp.GdbRspConnection()
# Request detaching from the target.
# This resumes execution, so we get the normal exit() status.
reply = connection.RspRequest('D')
self.assertEqual(reply, 'OK')
finally:
gdb_rsp.KillProcess(process)
def Main(): def Main():
index = sys.argv.index('--') index = sys.argv.index('--')
......
# 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.
# Flags: -expose-wasm --wasm-gdb-remote --wasm-pause-waiting-for-debugger test/debugging/wasm/gdb-server/test_files/test_float.js
import os
import re
import struct
import subprocess
import sys
import unittest
import gdb_rsp
import test_files.test_float as test_float
# These are set up by Main().
COMMAND = None
class Tests(unittest.TestCase):
def RunToWasm(self, connection):
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
breakpoint_addr = module_load_addr + test_float.FUNC_START_ADDR
# Set a breakpoint.
reply = connection.RspRequest('Z0,%x,1' % breakpoint_addr)
self.assertEqual(reply, 'OK')
# When we run the program, we should hit the breakpoint.
reply = connection.RspRequest('c')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
# Remove the breakpoint.
reply = connection.RspRequest('z0,%x,1' % breakpoint_addr)
self.assertEqual(reply, 'OK')
def test_loaded_modules(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
modules = gdb_rsp.GetLoadedModules(connection)
connection.Close()
assert(len(modules) > 0)
def test_wasm_local_float(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
self.RunToWasm(connection)
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
reply = connection.RspRequest('qWasmLocal:0;0')
value = struct.unpack('f', gdb_rsp.DecodeHex(reply))[0]
self.assertEqual(test_float.ARG_0, value)
reply = connection.RspRequest('qWasmLocal:0;1')
value = struct.unpack('f', gdb_rsp.DecodeHex(reply))[0]
self.assertEqual(test_float.ARG_1, value)
# invalid local
reply = connection.RspRequest('qWasmLocal:0;9')
self.assertEqual("E03", reply)
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. # Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import re
import socket import socket
import struct
import subprocess import subprocess
import time import time
import xml.etree.ElementTree
SOCKET_ADDR = ('localhost', 8765) SOCKET_ADDR = ('localhost', 8765)
SIGTRAP = 5
SIGSEGV = 11
RETURNCODE_KILL = -9
ARCH = 'wasm32'
REG_DEFS = {
ARCH: [('pc', 'Q'), ],
}
def EnsurePortIsAvailable(addr=SOCKET_ADDR): def EnsurePortIsAvailable(addr=SOCKET_ADDR):
# As a sanity check, check that the TCP port is available by binding to it # As a sanity check, check that the TCP port is available by binding to it
...@@ -21,6 +33,11 @@ def EnsurePortIsAvailable(addr=SOCKET_ADDR): ...@@ -21,6 +33,11 @@ def EnsurePortIsAvailable(addr=SOCKET_ADDR):
sock.bind(addr) sock.bind(addr)
sock.close() sock.close()
def RspChecksum(data):
checksum = 0
for char in data:
checksum = (checksum + ord(char)) & 0xff
return checksum
class GdbRspConnection(object): class GdbRspConnection(object):
...@@ -48,6 +65,40 @@ class GdbRspConnection(object): ...@@ -48,6 +65,40 @@ class GdbRspConnection(object):
raise Exception('Could not connect to the debug stub in %i seconds' raise Exception('Could not connect to the debug stub in %i seconds'
% timeout_in_seconds) % timeout_in_seconds)
def _GetReply(self):
reply = ''
message_finished = re.compile('#[0-9a-fA-F]{2}')
while True:
data = self._socket.recv(1024)
if len(data) == 0:
raise AssertionError('EOF on socket reached with '
'incomplete reply message: %r' % reply)
reply += data
if message_finished.match(reply[-3:]):
break
match = re.match('\+?\$([^#]*)#([0-9a-fA-F]{2})$', reply)
if match is None:
raise AssertionError('Unexpected reply message: %r' % reply)
reply_body = match.group(1)
checksum = match.group(2)
expected_checksum = '%02x' % RspChecksum(reply_body)
if checksum != expected_checksum:
raise AssertionError('Bad RSP checksum: %r != %r' %
(checksum, expected_checksum))
# Send acknowledgement.
self._socket.send('+')
return reply_body
# Send an rsp message, but don't wait for or expect a reply.
def RspSendOnly(self, data):
msg = '$%s#%02x' % (data, RspChecksum(data))
return self._socket.send(msg)
def RspRequest(self, data):
self.RspSendOnly(data)
reply = self._GetReply()
return reply
def Close(self): def Close(self):
self._socket.close() self._socket.close()
...@@ -71,3 +122,87 @@ def KillProcess(process): ...@@ -71,3 +122,87 @@ def KillProcess(process):
else: else:
raise raise
process.wait() process.wait()
class LaunchDebugStub(object):
def __init__(self, command):
self._proc = PopenDebugStub(command)
def __enter__(self):
try:
return GdbRspConnection()
except:
KillProcess(self._proc)
raise
def __exit__(self, exc_type, exc_value, traceback):
KillProcess(self._proc)
def AssertEquals(x, y):
if x != y:
raise AssertionError('%r != %r' % (x, y))
def DecodeHex(data):
assert len(data) % 2 == 0, data
return bytes(bytearray([int(data[index * 2 : (index + 1) * 2], 16) for index in xrange(len(data) // 2)]))
def EncodeHex(data):
return ''.join('%02x' % ord(byte) for byte in data)
def DecodeUInt64Array(data):
assert len(data) % 16 == 0, data
result = []
for index in xrange(len(data) // 16):
value = 0
for digit in xrange(7, -1, -1):
value = value * 256 + int(data[index * 16 + digit * 2 : index * 16 + (digit + 1) * 2], 16)
result.append(value)
return result
def AssertReplySignal(reply, signal):
AssertEquals(ParseThreadStopReply(reply)['signal'], signal)
def ParseThreadStopReply(reply):
match = re.match('T([0-9a-f]{2})thread-pcs:([0-9a-f]+);thread:([0-9a-f]+);$', reply)
if not match:
raise AssertionError('Bad thread stop reply: %r' % reply)
return {'signal': int(match.group(1), 16),
'thread_pc': int(match.group(2), 16),
'thread_id': int(match.group(3), 16)}
def CheckInstructionPtr(connection, expected_ip):
ip_value = DecodeRegs(connection.RspRequest('g'))['pc']
AssertEquals(ip_value, expected_ip)
def DecodeRegs(reply):
defs = REG_DEFS[ARCH]
names = [reg_name for reg_name, reg_fmt in defs]
fmt = ''.join([reg_fmt for reg_name, reg_fmt in defs])
values = struct.unpack_from(fmt, DecodeHex(reply))
return dict(zip(names, values))
def GetLoadedModules(connection):
modules = {}
reply = connection.RspRequest('qXfer:libraries:read')
AssertEquals(reply[0], 'l')
library_list = xml.etree.ElementTree.fromstring(reply[1:])
AssertEquals(library_list.tag, 'library-list')
for library in library_list:
AssertEquals(library.tag, 'library')
section = library.find('section')
address = section.get('address')
assert long(address) > 0
modules[long(address)] = library.get('name')
return modules
def GetLoadedModuleAddress(connection):
modules = GetLoadedModules(connection)
assert len(modules) > 0
return modules.keys()[0]
def ReadCodeMemory(connection, address, size):
reply = connection.RspRequest('m%x,%x' % (address, size))
assert not reply.startswith('E'), reply
return DecodeHex(reply)
# 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.
# Flags: -expose-wasm --wasm-gdb-remote --wasm-pause-waiting-for-debugger test/debugging/wasm/gdb-server/test_files/test_memory.js
import struct
import sys
import unittest
import gdb_rsp
import test_files.test_memory as test_memory
# These are set up by Main().
COMMAND = None
class Tests(unittest.TestCase):
# Test that reading from an unreadable address gives a sensible error.
def CheckReadMemoryAtInvalidAddr(self, connection):
mem_addr = 0xffffffff
result = connection.RspRequest('m%x,%x' % (mem_addr, 1))
self.assertEquals(result, 'E02')
def RunToWasm(self, connection, breakpoint_addr):
# Set a breakpoint.
reply = connection.RspRequest('Z0,%x,1' % breakpoint_addr)
self.assertEqual(reply, 'OK')
# When we run the program, we should hit the breakpoint.
reply = connection.RspRequest('c')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
# Remove the breakpoint.
reply = connection.RspRequest('z0,%x,1' % breakpoint_addr)
self.assertEqual(reply, 'OK')
def test_reading_and_writing_memory(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
breakpoint_addr = module_load_addr + test_memory.FUNC0_START_ADDR
self.RunToWasm(connection, breakpoint_addr)
self.CheckReadMemoryAtInvalidAddr(connection)
# Check reading code memory space.
expected_data = b'\0asm'
result = gdb_rsp.ReadCodeMemory(connection, module_load_addr, len(expected_data))
self.assertEqual(result, expected_data)
# Check reading instance memory at a valid range.
reply = connection.RspRequest('qWasmMem:0;%x;%x' % (32, 4))
value = struct.unpack('I', gdb_rsp.DecodeHex(reply))[0]
self.assertEquals(int(value), 0)
# Check reading instance memory at an invalid range.
reply = connection.RspRequest('qWasmMem:0;%x;%x' % (0xf0000000, 4))
self.assertEqual(reply, 'E03')
def test_wasm_global(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
breakpoint_addr = module_load_addr + test_memory.FUNC0_START_ADDR
self.RunToWasm(connection, breakpoint_addr)
# Check reading valid global.
reply = connection.RspRequest('qWasmGlobal:0;0')
value = struct.unpack('I', gdb_rsp.DecodeHex(reply))[0]
self.assertEqual(0, value)
# Check reading invalid global.
reply = connection.RspRequest('qWasmGlobal:0;9')
self.assertEqual("E03", reply)
def test_wasm_call_stack(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
breakpoint_addr = module_load_addr + test_memory.FUNC0_START_ADDR
self.RunToWasm(connection, breakpoint_addr)
reply = connection.RspRequest('qWasmCallStack')
stack = gdb_rsp.DecodeUInt64Array(reply)
assert(len(stack) > 2) # At least two Wasm frames, plus one or more JS frames.
self.assertEqual(stack[0], module_load_addr + test_memory.FUNC0_START_ADDR)
self.assertEqual(stack[1], module_load_addr + test_memory.FUNC1_RETURN_ADDR)
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 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.
# Flags: -expose-wasm --wasm-gdb-remote --wasm-pause-waiting-for-debugger test/debugging/wasm/gdb-server/test_files/test_basic.js
import re
import struct
import sys
import unittest
import gdb_rsp
import test_files.test_basic as test_basic
# These are set up by Main().
COMMAND = None
class Tests(unittest.TestCase):
def test_loaded_modules(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
modules = gdb_rsp.GetLoadedModules(connection)
connection.Close()
assert(len(modules) > 0)
def test_checking_thread_state(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
# Query wasm thread id
reply = connection.RspRequest('qfThreadInfo')
match = re.match('m([0-9])$', reply)
if match is None:
raise AssertionError('Bad active thread list reply: %r' % reply)
thread_id = int(match.group(1), 10)
# There should not be other threads.
reply = connection.RspRequest('qsThreadInfo')
self.assertEqual("l", reply)
# Test that valid thread should be alive.
reply = connection.RspRequest('T%d' % (thread_id))
self.assertEqual("OK", reply)
# Test invalid thread id.
reply = connection.RspRequest('T42')
self.assertEqual("E02", reply)
def test_wasm_local(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
breakpoint_addr = module_load_addr + test_basic.BREAK_ADDRESS_2
reply = connection.RspRequest('Z0,%x,1' % breakpoint_addr)
self.assertEqual("OK", reply)
reply = connection.RspRequest('c')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
reply = connection.RspRequest('qWasmLocal:0;0')
value = struct.unpack('I', gdb_rsp.DecodeHex(reply))[0]
self.assertEqual(test_basic.ARG_0, value)
reply = connection.RspRequest('qWasmLocal:0;1')
value = struct.unpack('I', gdb_rsp.DecodeHex(reply))[0]
self.assertEqual(test_basic.ARG_1, value)
# invalid local
reply = connection.RspRequest('qWasmLocal:0;9')
self.assertEqual("E03", reply)
def test_wasm_stack_value(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
breakpoint_addr = module_load_addr + test_basic.BREAK_ADDRESS_2
reply = connection.RspRequest('Z0,%x,1' % breakpoint_addr)
self.assertEqual("OK", reply)
reply = connection.RspRequest('c')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
reply = connection.RspRequest('qWasmStackValue:0;0')
value = struct.unpack('I', gdb_rsp.DecodeHex(reply))[0]
self.assertEqual(test_basic.ARG_0, value)
reply = connection.RspRequest('qWasmStackValue:0;1')
value = struct.unpack('I', gdb_rsp.DecodeHex(reply))[0]
self.assertEqual(test_basic.ARG_1, value)
# invalid index
reply = connection.RspRequest('qWasmStackValue:0;2')
self.assertEqual("E03", reply)
def test_modifying_code_is_disallowed(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
# Pick an arbitrary address in the code segment.
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
breakpoint_addr = module_load_addr + test_basic.BREAK_ADDRESS_1
# Writing to the code area should be disallowed.
data = '\x00'
write_command = 'M%x,%x:%s' % (breakpoint_addr, len(data), gdb_rsp.EncodeHex(data))
reply = connection.RspRequest(write_command)
self.assertEquals(reply, 'E03')
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 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.
# Flags: -expose-wasm --wasm-gdb-remote --wasm-pause-waiting-for-debugger test/debugging/wasm/gdb-server/test_files/test_basic.js
import sys
import unittest
import gdb_rsp
import test_files.test_basic as test_basic
# These are set up by Main().
COMMAND = None
class Tests(unittest.TestCase):
def test_single_step(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
bp_addr = module_load_addr + test_basic.BREAK_ADDRESS_0
reply = connection.RspRequest('Z0,%x,1' % bp_addr)
self.assertEqual("OK", reply)
reply = connection.RspRequest('c')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
# We expect 's' to stop at the next instruction.
reply = connection.RspRequest('s')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
tid = gdb_rsp.ParseThreadStopReply(reply)['thread_id']
self.assertEqual(tid, 1)
regs = gdb_rsp.DecodeRegs(connection.RspRequest('g'))
self.assertEqual(regs['pc'], module_load_addr + test_basic.BREAK_ADDRESS_1)
# Again.
reply = connection.RspRequest('s')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
tid = gdb_rsp.ParseThreadStopReply(reply)['thread_id']
self.assertEqual(tid, 1)
regs = gdb_rsp.DecodeRegs(connection.RspRequest('g'))
self.assertEqual(regs['pc'], module_load_addr + test_basic.BREAK_ADDRESS_2)
# Check that we can continue after single-stepping.
reply = connection.RspRequest('c')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGTRAP)
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 2020 the V8 project authors. All rights reserved.
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
load("test/mjsunit/wasm/wasm-module-builder.js"); load('test/mjsunit/wasm/wasm-module-builder.js');
var builder = new WasmModuleBuilder(); var builder = new WasmModuleBuilder();
builder.addFunction('mul', kSig_i_ii) builder
// input is 2 args of type int and output is int .addFunction('mul', kSig_i_ii)
.addBody([ // input is 2 args of type int and output is int
kExprLocalGet, 0, // local.get i0 .addBody([
kExprLocalGet, 1, // local.get i1 kExprLocalGet, 0, // local.get i0
kExprI32Mul]) // i32.sub i0 i1 kExprLocalGet, 1, // local.get i1
.exportFunc(); kExprI32Mul
]) // i32.mul i0 i1
.exportFunc();
const instance = builder.instantiate(); const instance = builder.instantiate();
const wasm_f = instance.exports.mul; const wasm_f = instance.exports.mul;
...@@ -23,11 +24,8 @@ function f() { ...@@ -23,11 +24,8 @@ function f() {
} }
try { try {
let val = 0; let val = f();
while (true) { f();
val += f(); } catch (e) {
} print('*exception:* ' + e);
}
catch (e) {
print('*exception:* ' + e);
} }
# 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.
# 0x00 (module
# 0x08 [type]
# (type $type0 (func (param i32 i32) (result i32)))
# 0x11 [function]
# 0x15 (export "mul" (func $func0))
# 0x1e [code]
# 0x20 (func $func0 (param $var0 i32) (param $var1 i32) (result i32)
# 0x23 get_local $var0
# 0x25 get_local $var1
# 0x27 i32.mul
# )
# )
# 0x29 [name]
BREAK_ADDRESS_0 = 0x0023
BREAK_ADDRESS_1 = 0x0025
BREAK_ADDRESS_2 = 0x0027
ARG_0 = 21
ARG_1 = 2
// 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.
load('test/mjsunit/wasm/wasm-module-builder.js');
var builder = new WasmModuleBuilder();
builder
.addFunction('mul', kSig_f_ff)
// input is 2 args of type float and output is float
.addBody([
kExprLocalGet, 0, // local.get f0
kExprLocalGet, 1, // local.get f1
kExprF32Mul, // f32.mul i0 i1
])
.exportFunc();
const instance = builder.instantiate();
const wasm_f = instance.exports.mul;
function f() {
var result = wasm_f(12.0, 3.5);
return result;
}
try {
let val = f();
print('float result: ' + val);
} catch (e) {
print('*exception:* ' + e);
}
# 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.
# 0x00 (module
# 0x08 [type]
# (type $type0 (func (param f32 f32) (result f32)))
# 0x11 [function]
# 0x15 (export "mul" (func $func0))
# 0x1e [code]
# 0x20 (func $func0 (param $var0 f32) (param $var1 f32) (result f32)
# 0x23 get_local $var0
# 0x25 get_local $var1
# 0x27 f32.mul
# )
# )
# 0x29 [name]
ARG_0 = 12.0
ARG_1 = 3.5
FUNC_START_ADDR = 0x23
// 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.
load('test/mjsunit/wasm/wasm-module-builder.js');
var builder = new WasmModuleBuilder();
builder.addGlobal(kWasmI32).exportAs('g_n');
builder.addMemory(32, 128).exportMemoryAs('mem')
var func_a_idx =
builder.addFunction('wasm_A', kSig_v_i).addBody([kExprNop, kExprNop]).index;
// wasm_B calls wasm_A <param0> times.
builder.addFunction('wasm_B', kSig_v_i)
.addBody([
kExprLoop,
kWasmStmt, // while
kExprLocalGet,
0, // -
kExprIf,
kWasmStmt, // if <param0> != 0
kExprLocalGet,
0, // -
kExprI32Const,
1, // -
kExprI32Sub, // -
kExprLocalSet,
0, // decrease <param0>
...wasmI32Const(1024), // some longer i32 const (2 byte imm)
kExprCallFunction,
func_a_idx, // -
kExprBr,
1, // continue
kExprEnd, // -
kExprEnd, // break
])
.exportAs('main');
const instance = builder.instantiate();
const wasm_main = instance.exports.main;
function f() {
wasm_main(42);
}
f();
# 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.
# 0x00 (module
# 0x08 [type]
# 0x0a (type $type0 (func (param i32)))
# 0x0f (type $type1 (func (param i32)))
# 0x13 [function]
# 0x18 [memory]
# (memory (;0;) 32 128)
# 0x1f [global]
# 0x27 (global $global0 i32 (i32.const 0))
# 0x29 (export "g_n" (global $global0))
# 0x30 (export "mem" (memory 0))
# 0x36 (export "main" (func $func1))
# 0x3d [code]
# 0x3f (func $func0 (param $var0 i32)
# 0x42 nop
# 0x43 nop
# )
# 0x45 (func $func1 (param $var0 i32)
# 0x47 loop $label0
# 0x49 get_local $var0
# 0x4b if
# 0x4d get_local $var0
# 0x4f i32.const 1
# 0x51 i32.sub
# 0x52 set_local $var0
# 0x54 i32.const 1024
# 0x57 call $func0
# 0x59 br $label0
# 0x5b end
# 0x5c end $label0
# )
# )
# 0x5e [name]
MEM_MIN = 32
MEM_MAX = 128
FUNC0_START_ADDR = 0x42
FUNC1_RETURN_ADDR = 0x59
FUNC1_START_ADDR = 0x47
// 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.
load('test/mjsunit/wasm/wasm-module-builder.js');
var builder = new WasmModuleBuilder();
builder.addMemory(32, 128).exportMemoryAs('mem')
var func_a_idx =
builder.addFunction('wasm_A', kSig_v_v).addBody([
kExprI32Const, 0, // i32.const 0
kExprI32Const, 42, // i32.const 42
kExprI32StoreMem, 0, 0xff, 0xff, 0xff, 0xff, 0x0f, // i32.store offset = -1
]).index;
builder.addFunction('main', kSig_i_v).addBody([
kExprCallFunction, func_a_idx, // call $wasm_A
kExprI32Const, 0 // i32.const 0
])
.exportFunc();
const instance = builder.instantiate();
const main_f = instance.exports.main;
function f() {
var result = main_f();
return result;
}
f();
# 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.
# 0x00 (module
# 0x08 [type]
# 0x0a (type $type0 (func))
# 0x0e (type $type1 (func (result i32)))
# 0x12 [function]
# 0x17 [memory]
# 0x19 (memory $memory0 32 128)
# 0x1e [export]
# 0x20 (export "mem" (memory $memory0))
# 0x27 (export "main" (func $func1))
# 0x2e [code]
# 0x30 (func $func0
# 0x33 i32.const 0
# 0x35 i32.const 42
# 0x37 i32.store offset=-1 align=1
# 0x3e )
# 0x3f (func $func1 (result i32)
# 0x41 call $func0
# 0x43 i32.const 0
# 0x45 )
# 0x46 ...
# )
TRAP_ADDRESS = 0x0037
# 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.
# Flags: -expose-wasm --wasm-gdb-remote --wasm-pause-waiting-for-debugger test/debugging/wasm/gdb-server/test_files/test_trap.js
import sys
import unittest
import gdb_rsp
import test_files.test_trap as test_trap
# These are set up by Main().
COMMAND = None
class Tests(unittest.TestCase):
def test_trap(self):
with gdb_rsp.LaunchDebugStub(COMMAND) as connection:
module_load_addr = gdb_rsp.GetLoadedModuleAddress(connection)
reply = connection.RspRequest('c')
gdb_rsp.AssertReplySignal(reply, gdb_rsp.SIGSEGV)
tid = gdb_rsp.ParseThreadStopReply(reply)['thread_id']
self.assertEqual(tid, 1)
regs = gdb_rsp.DecodeRegs(connection.RspRequest('g'))
self.assertEqual(regs['pc'], module_load_addr + test_trap.TRAP_ADDRESS)
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()
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