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/+/1941139 Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Clemens Backes <clemensb@chromium.org> Commit-Queue: Paolo Severini <paolosev@microsoft.com> Cr-Commit-Position: refs/heads/master@{#67412}
This commit is contained in:
parent
97a4b795be
commit
74e9318689
2
BUILD.gn
2
BUILD.gn
@ -3150,6 +3150,8 @@ v8_source_set("v8_base_without_compiler") {
|
||||
"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/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) {
|
||||
// If we still have something to process
|
||||
if (*in) {
|
||||
const char* start = in;
|
||||
int len = 0;
|
||||
size_t len = 0;
|
||||
// Keep moving forward for all valid chars
|
||||
while (*in && (strchr(delim, *in) == nullptr)) {
|
||||
len++;
|
||||
|
@ -31,21 +31,20 @@ bool HexToUInt8(const char chars[2], uint8_t* byte);
|
||||
// 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);
|
||||
|
||||
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
|
||||
// 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 {
|
||||
public:
|
||||
wasm_addr_t(uint32_t module_id, uint32_t offset)
|
||||
|
@ -34,6 +34,8 @@ class GdbServerThread : public v8::base::Thread {
|
||||
// closes any active debugging session.
|
||||
void Stop();
|
||||
|
||||
Target& GetTarget() { return *target_; }
|
||||
|
||||
private:
|
||||
void CleanupThread();
|
||||
|
||||
|
@ -4,19 +4,124 @@
|
||||
|
||||
#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/wasm/wasm-engine.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(
|
||||
@ -27,57 +132,290 @@ std::unique_ptr<GdbServer> GdbServer::Create() {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// All the following methods require interaction with the actual V8 Wasm engine.
|
||||
// They will be implemented later.
|
||||
void GdbServer::RunMessageLoopOnPause() { task_runner_->Run(); }
|
||||
|
||||
std::vector<GdbServer::WasmModuleInfo> GdbServer::GetLoadedModules() const {
|
||||
// TODO(paolosev)
|
||||
return {};
|
||||
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::GetWasmGlobal(uint32_t wasm_module_id, uint32_t index,
|
||||
uint64_t* value) {
|
||||
// TODO(paolosev)
|
||||
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::GetWasmLocal(uint32_t wasm_module_id, uint32_t frame_index,
|
||||
uint32_t index, uint64_t* value) {
|
||||
// TODO(paolosev)
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t GdbServer::GetWasmMemory(uint32_t wasm_module_id, uint32_t offset,
|
||||
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) {
|
||||
// TODO(paolosev)
|
||||
return 0;
|
||||
// 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 address, uint8_t* buffer,
|
||||
uint32_t GdbServer::GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer,
|
||||
uint32_t size) {
|
||||
// TODO(paolosev)
|
||||
return 0;
|
||||
// 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 module_id, uint32_t offset) {
|
||||
// TODO(paolosev)
|
||||
return false;
|
||||
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 module_id, uint32_t offset) {
|
||||
// TODO(paolosev)
|
||||
return false;
|
||||
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 {
|
||||
// TODO(paolosev)
|
||||
return {};
|
||||
// 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
|
||||
|
@ -5,14 +5,18 @@
|
||||
#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.
|
||||
@ -37,26 +41,35 @@ class GdbServer {
|
||||
uint32_t module_id;
|
||||
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
|
||||
// by {wasm_module_id}.
|
||||
bool GetWasmGlobal(uint32_t wasm_module_id, uint32_t index, uint64_t* value);
|
||||
// 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 {wasm_module_id}.
|
||||
bool GetWasmLocal(uint32_t wasm_module_id, uint32_t frame_index,
|
||||
uint32_t index, uint64_t* value);
|
||||
// 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 {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.
|
||||
// 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
|
||||
// Wasm module identified by {wasm_module_id}.
|
||||
// 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);
|
||||
@ -64,21 +77,119 @@ class GdbServer {
|
||||
// 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 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
|
||||
// by {wasm_module_id}.
|
||||
// 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.
|
||||
std::vector<wasm_addr_t> GetWasmCallStack() const;
|
||||
|
||||
private:
|
||||
GdbServer() {}
|
||||
// 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);
|
||||
};
|
||||
|
||||
|
@ -36,18 +36,20 @@ bool Session::GetChar(char* ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Session::SendPacket(Packet* pkt) {
|
||||
bool Session::SendPacket(Packet* pkt, bool expect_ack) {
|
||||
char ch;
|
||||
do {
|
||||
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()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If ACKs are off, we are done.
|
||||
if (!ack_enabled_) {
|
||||
if (!expect_ack || !ack_enabled_) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ class V8_EXPORT_PRIVATE Session {
|
||||
explicit Session(TransportBase* transport);
|
||||
|
||||
// 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.
|
||||
bool GetPacket(Packet* packet);
|
||||
|
@ -19,16 +19,25 @@ 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) {}
|
||||
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=4000;vContSupported-;qXfer:libraries:read+;";
|
||||
"PacketSize=1000;vContSupported-;qXfer:libraries:read+;";
|
||||
|
||||
query_properties_["Attached"] = "1";
|
||||
|
||||
@ -51,8 +60,32 @@ void Target::InitQueryPropertyMap() {
|
||||
}
|
||||
|
||||
void Target::Terminate() {
|
||||
// Executed in the Isolate thread.
|
||||
status_ = Status::Terminated;
|
||||
// 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) {
|
||||
@ -60,6 +93,7 @@ void Target::Run(Session* session) {
|
||||
session_ = session;
|
||||
do {
|
||||
WaitForDebugEvent();
|
||||
ProcessDebugEvent();
|
||||
ProcessCommands();
|
||||
} while (!IsTerminated() && session_->IsConnected());
|
||||
session_ = nullptr;
|
||||
@ -68,7 +102,7 @@ void Target::Run(Session* session) {
|
||||
void Target::WaitForDebugEvent() {
|
||||
// Executed in the GdbServer thread.
|
||||
|
||||
if (status_ != Status::Terminated) {
|
||||
if (status_ == Status::Running) {
|
||||
// Wait for either:
|
||||
// * the thread to fault (or single-step)
|
||||
// * an interrupt from LLDB
|
||||
@ -76,11 +110,53 @@ void Target::WaitForDebugEvent() {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@ -99,13 +175,16 @@ void Target::ProcessCommands() {
|
||||
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();
|
||||
cur_signal_ = 0; // // Reset the signal value
|
||||
gdb_server_->QuitMessageLoopOnPause();
|
||||
return;
|
||||
|
||||
case ProcessPacketResult::Kill:
|
||||
@ -116,6 +195,10 @@ void Target::ProcessCommands() {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
if (!session_->IsConnected()) {
|
||||
debugger_initial_suspension_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
|
||||
@ -152,9 +235,8 @@ Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
|
||||
// IN : $c
|
||||
// OUT: A Stop-reply packet is sent later, when the execution halts.
|
||||
case 'c':
|
||||
// TODO(paolosev) - Not implemented yet
|
||||
err = ErrorCode::Failed;
|
||||
break;
|
||||
SetStatus(Status::Running);
|
||||
return ProcessPacketResult::Continue;
|
||||
|
||||
// Detaches the debugger from this target
|
||||
// IN : $D
|
||||
@ -276,9 +358,11 @@ Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
|
||||
// IN : $s
|
||||
// OUT: A Stop-reply packet is sent later, when the execution halts.
|
||||
case 's': {
|
||||
// TODO(paolosev) - Not implemented yet
|
||||
err = ErrorCode::Failed;
|
||||
break;
|
||||
if (status_ == Status::Suspended) {
|
||||
gdb_server_->PrepareStep();
|
||||
SetStatus(Status::Running);
|
||||
}
|
||||
return ProcessPacketResult::Continue;
|
||||
}
|
||||
|
||||
// Find out if the thread 'id' is alive.
|
||||
@ -299,15 +383,14 @@ Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
|
||||
}
|
||||
|
||||
// Z: Adds a breakpoint
|
||||
// IN : $Ztype,addr,kind
|
||||
// IN : $Z<type>,<addr>,<kind>
|
||||
// <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
|
||||
// OUT: $OK (success) or $Enn (error)
|
||||
case 'Z': {
|
||||
// Only software breakpoints are supported.
|
||||
uint64_t breakpoint_type; // 0: sw breakpoint,
|
||||
// 1: hw breakpoint,
|
||||
// 2: watchpoint
|
||||
uint64_t breakpoint_type;
|
||||
uint64_t breakpoint_address;
|
||||
uint64_t breakpoint_kind; // Ignored for Wasm.
|
||||
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)) {
|
||||
@ -327,7 +410,8 @@ Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
|
||||
}
|
||||
|
||||
// z: Removes a breakpoint
|
||||
// IN : $ztype,addr,kind
|
||||
// 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;
|
||||
@ -353,8 +437,6 @@ Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
|
||||
|
||||
// If the command is not recognized, ignore it by sending an empty reply.
|
||||
default: {
|
||||
std::string str;
|
||||
pkt_in->GetString(&str);
|
||||
TRACE_GDB_REMOTE("Unknown command: %s\n", pkt_in->GetPayload());
|
||||
}
|
||||
}
|
||||
@ -428,25 +510,29 @@ Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
|
||||
// consecutive 8-bytes blocks).
|
||||
std::vector<std::string> toks = StringSplit(str, ":;");
|
||||
if (toks[0] == "WasmCallStack") {
|
||||
std::vector<wasm_addr_t> callStackPCs = gdb_server_->GetWasmCallStack();
|
||||
pkt_out->AddBlock(
|
||||
callStackPCs.data(),
|
||||
static_cast<uint32_t>(sizeof(wasm_addr_t) * callStackPCs.size()));
|
||||
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:moduleId;index
|
||||
// IN : $qWasmGlobal:frame_index;index
|
||||
// OUT: $xx..xx
|
||||
if (toks[0] == "WasmGlobal") {
|
||||
if (toks.size() == 3) {
|
||||
uint32_t module_id =
|
||||
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));
|
||||
uint64_t value = 0;
|
||||
if (gdb_server_->GetWasmGlobal(module_id, index, &value)) {
|
||||
pkt_out->AddBlock(&value, sizeof(value));
|
||||
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;
|
||||
@ -456,19 +542,39 @@ Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
|
||||
}
|
||||
|
||||
// Get a Wasm local value in the stack frame specified.
|
||||
// IN : $qWasmLocal:moduleId;frameIndex;index
|
||||
// IN : $qWasmLocal:frame_index;index
|
||||
// OUT: $xx..xx
|
||||
if (toks[0] == "WasmLocal") {
|
||||
if (toks.size() == 4) {
|
||||
uint32_t module_id =
|
||||
static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
|
||||
if (toks.size() == 3) {
|
||||
uint32_t frame_index =
|
||||
static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
|
||||
static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
|
||||
uint32_t index =
|
||||
static_cast<uint32_t>(strtol(toks[3].data(), nullptr, 10));
|
||||
uint64_t value = 0;
|
||||
if (gdb_server_->GetWasmLocal(module_id, frame_index, index, &value)) {
|
||||
pkt_out->AddBlock(&value, sizeof(value));
|
||||
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;
|
||||
@ -478,11 +584,11 @@ Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
|
||||
}
|
||||
|
||||
// Read Wasm memory.
|
||||
// IN : $qWasmMem:memId;addr;len
|
||||
// IN : $qWasmMem:frame_index;addr;len
|
||||
// OUT: $xx..xx
|
||||
if (toks[0] == "WasmMem") {
|
||||
if (toks.size() == 4) {
|
||||
uint32_t module_id =
|
||||
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));
|
||||
@ -493,7 +599,7 @@ Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
|
||||
}
|
||||
uint8_t buff[Transport::kBufSize];
|
||||
uint32_t read =
|
||||
gdb_server_->GetWasmMemory(module_id, address, buff, length);
|
||||
gdb_server_->GetWasmMemory(frame_index, address, buff, length);
|
||||
if (read > 0) {
|
||||
pkt_out->AddBlock(buff, read);
|
||||
return ErrorCode::None;
|
||||
@ -536,7 +642,36 @@ void Target::SetStopReply(Packet* pkt_out) const {
|
||||
pkt_out->AddNumberSep(kThreadId, ';');
|
||||
}
|
||||
|
||||
wasm_addr_t Target::GetCurrentPc() const { return wasm_addr_t(0); }
|
||||
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
|
||||
|
@ -33,7 +33,23 @@ class Target {
|
||||
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();
|
||||
@ -43,11 +59,15 @@ class Target {
|
||||
// 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 {
|
||||
@ -69,22 +89,46 @@ class Target {
|
||||
// (continue), 's' (step) and '?' (query halt reason) commands.
|
||||
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_;
|
||||
|
||||
enum class Status { Running, Terminated };
|
||||
std::atomic<Status> status_;
|
||||
|
||||
// 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.
|
||||
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);
|
||||
};
|
||||
|
||||
|
393
src/debug/wasm/gdb-server/wasm-module-debug.cc
Normal file
393
src/debug/wasm/gdb-server/wasm-module-debug.cc
Normal file
@ -0,0 +1,393 @@
|
||||
// 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_COMPILED: {
|
||||
// A standard frame may include many summarized frames, due to inlining.
|
||||
std::vector<FrameSummary> frames;
|
||||
StandardFrame::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.IsWasmCompiled()) {
|
||||
FrameSummary::WasmCompiledFrameSummary const& wasm_compiled =
|
||||
summary.AsWasmCompiled();
|
||||
offset =
|
||||
GetWasmFunctionOffset(wasm_compiled.wasm_instance()->module(),
|
||||
wasm_compiled.function_index()) +
|
||||
wasm_compiled.byte_offset();
|
||||
script = wasm_compiled.script();
|
||||
|
||||
bool zeroth_frame = call_stack.empty();
|
||||
if (!zeroth_frame) {
|
||||
const NativeModule* native_module = wasm_compiled.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::WASM_INTERPRETER_ENTRY:
|
||||
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_COMPILED:
|
||||
case StackFrame::WASM_INTERPRETER_ENTRY: {
|
||||
// A standard frame may include many summarized frames, due to inlining.
|
||||
std::vector<FrameSummary> frames;
|
||||
StandardFrame::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.IsWasmCompiled()) {
|
||||
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.IsWasmCompiled()) {
|
||||
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::kWasmAnyRef.kind():
|
||||
case wasm::kWasmFuncRef.kind():
|
||||
case wasm::kWasmNullRef.kind():
|
||||
case wasm::kWasmExnRef.kind():
|
||||
case wasm::kWasmBottom.kind():
|
||||
default:
|
||||
// Not supported
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gdb_server
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
105
src/debug/wasm/gdb-server/wasm-module-debug.h
Normal file
105
src/debug/wasm/gdb-server/wasm-module-debug.h
Normal file
@ -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_
|
@ -1435,6 +1435,7 @@ DEFINE_BOOL(multi_mapped_mock_allocator, false,
|
||||
#define DEFAULT_WASM_GDB_REMOTE_PORT 8765
|
||||
DEFINE_BOOL(wasm_gdb_remote, false,
|
||||
"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,
|
||||
"default port for WebAssembly debugging with LLDB.")
|
||||
DEFINE_BOOL(wasm_pause_waiting_for_debugger, false,
|
||||
|
@ -868,9 +868,20 @@ void WasmEngine::AddIsolate(Isolate* isolate) {
|
||||
};
|
||||
isolate->heap()->AddGCEpilogueCallback(callback, v8::kGCTypeMarkSweepCompact,
|
||||
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) {
|
||||
#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_);
|
||||
auto it = isolates_.find(isolate);
|
||||
DCHECK_NE(isolates_.end(), it);
|
||||
@ -956,6 +967,7 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
|
||||
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
|
||||
if (FLAG_wasm_gdb_remote && !gdb_server_) {
|
||||
gdb_server_ = gdb_server::GdbServer::Create();
|
||||
gdb_server_->AddIsolate(isolate);
|
||||
}
|
||||
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
|
||||
|
||||
|
@ -79,7 +79,8 @@ class PYTestLoader(testsuite.GenericTestLoader):
|
||||
|
||||
@property
|
||||
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
|
||||
def extensions(self):
|
||||
|
3
test/debugging/wasm/gdb-server/OWNERS
Normal file
3
test/debugging/wasm/gdb-server/OWNERS
Normal file
@ -0,0 +1,3 @@
|
||||
paolosev@microsoft.com
|
||||
|
||||
# COMPONENT: Blink>JavaScript>WebAssembly
|
58
test/debugging/wasm/gdb-server/breakpoints.py
Normal file
58
test/debugging/wasm/gdb-server/breakpoints.py
Normal file
@ -0,0 +1,58 @@
|
||||
# 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()
|
@ -1,24 +1,27 @@
|
||||
# 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
|
||||
# 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 subprocess
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
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_disconnect(self):
|
||||
process = gdb_rsp.PopenDebugStub(COMMAND)
|
||||
try:
|
||||
# Connect and record the instruction pointer.
|
||||
# Connect.
|
||||
connection = gdb_rsp.GdbRspConnection()
|
||||
connection.Close()
|
||||
# Reconnect 3 times.
|
||||
@ -28,6 +31,29 @@ class Tests(unittest.TestCase):
|
||||
finally:
|
||||
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():
|
||||
index = sys.argv.index('--')
|
||||
|
71
test/debugging/wasm/gdb-server/float.py
Normal file
71
test/debugging/wasm/gdb-server/float.py
Normal file
@ -0,0 +1,71 @@
|
||||
# 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()
|
@ -1,13 +1,25 @@
|
||||
# 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
|
||||
# found in the LICENSE file.
|
||||
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import time
|
||||
import xml.etree.ElementTree
|
||||
|
||||
SOCKET_ADDR = ('localhost', 8765)
|
||||
|
||||
SIGTRAP = 5
|
||||
SIGSEGV = 11
|
||||
RETURNCODE_KILL = -9
|
||||
|
||||
ARCH = 'wasm32'
|
||||
REG_DEFS = {
|
||||
ARCH: [('pc', 'Q'), ],
|
||||
}
|
||||
|
||||
|
||||
def EnsurePortIsAvailable(addr=SOCKET_ADDR):
|
||||
# As a sanity check, check that the TCP port is available by binding to it
|
||||
@ -21,6 +33,11 @@ def EnsurePortIsAvailable(addr=SOCKET_ADDR):
|
||||
sock.bind(addr)
|
||||
sock.close()
|
||||
|
||||
def RspChecksum(data):
|
||||
checksum = 0
|
||||
for char in data:
|
||||
checksum = (checksum + ord(char)) & 0xff
|
||||
return checksum
|
||||
|
||||
class GdbRspConnection(object):
|
||||
|
||||
@ -48,6 +65,40 @@ class GdbRspConnection(object):
|
||||
raise Exception('Could not connect to the debug stub in %i 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):
|
||||
self._socket.close()
|
||||
|
||||
@ -71,3 +122,87 @@ def KillProcess(process):
|
||||
else:
|
||||
raise
|
||||
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)
|
||||
|
96
test/debugging/wasm/gdb-server/memory.py
Normal file
96
test/debugging/wasm/gdb-server/memory.py
Normal file
@ -0,0 +1,96 @@
|
||||
# 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()
|
109
test/debugging/wasm/gdb-server/status.py
Normal file
109
test/debugging/wasm/gdb-server/status.py
Normal file
@ -0,0 +1,109 @@
|
||||
# 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()
|
56
test/debugging/wasm/gdb-server/stepping.py
Normal file
56
test/debugging/wasm/gdb-server/stepping.py
Normal file
@ -0,0 +1,56 @@
|
||||
# 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()
|
@ -1,33 +0,0 @@
|
||||
|
||||
// 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);
|
||||
}
|
31
test/debugging/wasm/gdb-server/test_files/test_basic.js
Normal file
31
test/debugging/wasm/gdb-server/test_files/test_basic.js
Normal file
@ -0,0 +1,31 @@
|
||||
// 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_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.mul 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 = f();
|
||||
f();
|
||||
} catch (e) {
|
||||
print('*exception:* ' + e);
|
||||
}
|
23
test/debugging/wasm/gdb-server/test_files/test_basic.py
Normal file
23
test/debugging/wasm/gdb-server/test_files/test_basic.py
Normal file
@ -0,0 +1,23 @@
|
||||
# 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
|
31
test/debugging/wasm/gdb-server/test_files/test_float.js
Normal file
31
test/debugging/wasm/gdb-server/test_files/test_float.js
Normal file
@ -0,0 +1,31 @@
|
||||
// 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);
|
||||
}
|
21
test/debugging/wasm/gdb-server/test_files/test_float.py
Normal file
21
test/debugging/wasm/gdb-server/test_files/test_float.py
Normal file
@ -0,0 +1,21 @@
|
||||
# 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
|
48
test/debugging/wasm/gdb-server/test_files/test_memory.js
Normal file
48
test/debugging/wasm/gdb-server/test_files/test_memory.js
Normal file
@ -0,0 +1,48 @@
|
||||
// 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();
|
43
test/debugging/wasm/gdb-server/test_files/test_memory.py
Normal file
43
test/debugging/wasm/gdb-server/test_files/test_memory.py
Normal file
@ -0,0 +1,43 @@
|
||||
# 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
|
31
test/debugging/wasm/gdb-server/test_files/test_trap.js
Normal file
31
test/debugging/wasm/gdb-server/test_files/test_trap.js
Normal file
@ -0,0 +1,31 @@
|
||||
// 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();
|
28
test/debugging/wasm/gdb-server/test_files/test_trap.py
Normal file
28
test/debugging/wasm/gdb-server/test_files/test_trap.py
Normal file
@ -0,0 +1,28 @@
|
||||
# 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
|
37
test/debugging/wasm/gdb-server/trap.py
Normal file
37
test/debugging/wasm/gdb-server/trap.py
Normal file
@ -0,0 +1,37 @@
|
||||
# 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()
|
Loading…
Reference in New Issue
Block a user