Commit b47893fc authored by Paolo Severini's avatar Paolo Severini Committed by Commit Bot

Wasm debugging with LLDB: decode and execute GDB-remote commands

This changelist adds to the GDB stub (in class wasm::gdb_server::Target) the
logic to decode and execute GDB-remote commands and to format response packets
to be sent back to the debugger.
Here most of the commands still act as a NOOP; the actual implementation
requires interactions with the Wasm engine and will be implemented in the next
CL of this series.

Build with: v8_enable_wasm_gdb_remote_debugging = true
Run with: --wasm-gdb-remote

Bug: chromium:1010467
Change-Id: Icfa63be9e1eaa657c05876d0d4e86927e0885b90
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1938466
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67200}
parent fcf2ce35
......@@ -54,6 +54,39 @@ bool NibbleToUInt8(char ch, uint8_t* byte) {
return false;
}
std::vector<std::string> StringSplit(const string& instr, const char* delim) {
std::vector<std::string> result;
const char* in = instr.data();
if (nullptr == in) return result;
// Check if we have nothing to do
if (nullptr == delim) {
result.push_back(string(in));
return result;
}
while (*in) {
// Toss all preceeding delimiters
while (*in && strchr(delim, *in)) in++;
// If we still have something to process
if (*in) {
const char* start = in;
int len = 0;
// Keep moving forward for all valid chars
while (*in && (strchr(delim, *in) == nullptr)) {
len++;
in++;
}
// Build this token and add it to the array.
result.push_back(string{start, len});
}
}
return result;
}
std::string Mem2Hex(const uint8_t* mem, size_t count) {
std::vector<char> result(count * 2 + 1);
for (size_t i = 0; i < count; i++) UInt8ToHex(*mem++, &result[i * 2]);
......
......@@ -36,6 +36,35 @@ bool NibbleToUInt8(char ch, uint8_t* byte);
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 |
// +---------------+---------------+
class wasm_addr_t {
public:
wasm_addr_t(uint32_t module_id, uint32_t offset)
: module_id_(module_id), offset_(offset) {}
explicit wasm_addr_t(uint64_t address)
: module_id_(address >> 32), offset_(address & 0xffffffff) {}
inline uint32_t ModuleId() const { return module_id_; }
inline uint32_t Offset() const { return offset_; }
inline operator uint64_t() const {
return static_cast<uint64_t>(module_id_) << 32 | offset_;
}
private:
uint32_t module_id_;
uint32_t offset_;
};
} // namespace gdb_server
} // namespace wasm
} // namespace internal
......
......@@ -33,6 +33,53 @@ GdbServer::~GdbServer() {
}
}
// All the following methods require interaction with the actual V8 Wasm engine.
// They will be implemented later.
std::vector<GdbServer::WasmModuleInfo> GdbServer::GetLoadedModules() const {
// TODO(paolosev)
return {};
}
bool GdbServer::GetWasmGlobal(uint32_t wasm_module_id, uint32_t index,
uint64_t* value) {
// TODO(paolosev)
return false;
}
bool GdbServer::GetWasmLocal(uint32_t wasm_module_id, uint32_t frame_index,
uint32_t index, uint64_t* value) {
// TODO(paolosev)
return false;
}
uint32_t GdbServer::GetWasmMemory(uint32_t wasm_module_id, uint32_t offset,
uint8_t* buffer, uint32_t size) {
// TODO(paolosev)
return 0;
}
uint32_t GdbServer::GetWasmModuleBytes(wasm_addr_t address, uint8_t* buffer,
uint32_t size) {
// TODO(paolosev)
return 0;
}
bool GdbServer::AddBreakpoint(uint32_t module_id, uint32_t offset) {
// TODO(paolosev)
return false;
}
bool GdbServer::RemoveBreakpoint(uint32_t module_id, uint32_t offset) {
// TODO(paolosev)
return false;
}
std::vector<wasm_addr_t> GdbServer::GetWasmCallStack() const {
// TODO(paolosev)
return {};
}
} // namespace gdb_server
} // namespace wasm
} // namespace internal
......
......@@ -31,6 +31,49 @@ class GdbServer {
// called once, when the Wasm engine shuts down.
~GdbServer();
// Queries the set of the Wasm modules currently loaded. Each module is
// identified by a unique integer module id.
struct WasmModuleInfo {
uint32_t module_id;
std::string module_name;
};
std::vector<WasmModuleInfo> GetLoadedModules() const;
// 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);
// 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);
// Reads {size} bytes, starting from {offset}, from the Memory instance
// associated to the Wasm module identified by {wasm_module_id}.
// 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);
// Reads {size} bytes, starting from {address}, from the Code space of the
// Wasm module identified by {wasm_module_id}.
// Returns the number of bytes copied to {buffer}, or 0 is case of error.
uint32_t GetWasmModuleBytes(wasm_addr_t address, uint8_t* buffer,
uint32_t size);
// Inserts a breakpoint at the offset {offset} of the Wasm module identified
// by {wasm_module_id}.
// Returns true if the breakpoint was successfully added.
bool AddBreakpoint(uint32_t 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);
// Returns the current call stack as a vector of program counters.
std::vector<wasm_addr_t> GetWasmCallStack() const;
private:
GdbServer() {}
......
......@@ -43,6 +43,15 @@ void Packet::AddBlock(const void* ptr, uint32_t len) {
}
}
void Packet::AddString(const char* str) {
DCHECK(str);
while (*str) {
AddRawChar(*str);
str++;
}
}
void Packet::AddHexString(const char* str) {
DCHECK(str);
......
......@@ -42,9 +42,12 @@ class V8_EXPORT_PRIVATE Packet {
// separator (non hex digit).
void AddNumberSep(uint64_t val, char sep);
// Add a raw string.
void AddString(const char* str);
// Add a string stored as a stream of ASCII hex digit pairs. It is safe
// to use any non-null character in this stream. If this does not terminate
// the packet, there should be a seperator (non hex digit) immediately
// the packet, there should be a separator (non hex digit) immediately
// following.
void AddHexString(const char* str);
......
This diff is collapsed.
......@@ -6,7 +6,9 @@
#define V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
#include <atomic>
#include <map>
#include "src/base/macros.h"
#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
namespace v8 {
namespace internal {
......@@ -32,6 +34,10 @@ class Target {
bool IsTerminated() const { return status_ == Status::Terminated; }
private:
// Initializes a map used to make fast lookups when handling query packets
// that have a constant response.
void InitQueryPropertyMap();
// Blocks waiting for one of these two events to occur:
// - A network packet arrives from the debugger, or the debugger connection is
// closed;
......@@ -42,19 +48,43 @@ class Target {
// This method should be called when the debuggee has suspended its execution.
void ProcessCommands();
// This function always succeedes, since all errors
// are reported as an error string of "E<##>" where
// the two digit number. The error codes are not
// not documented, so this implementation uses
// ErrDef as errors codes. This function returns
// true a request to continue (or step) is processed.
bool ProcessPacket(const Packet* pktIn, Packet* pktOut);
enum class ErrorCode { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 };
enum class ProcessPacketResult {
Paused, // The command was processed, debuggee still paused.
Continue, // The debuggee should resume execution.
Detach, // Request to detach from the debugger.
Kill // Request to terminate the debuggee process.
};
// This function always succeedes, since all errors are reported as an error
// string "Exx" where xx is a two digit number.
// The return value indicates if the target can resume execution or it is
// still paused.
ProcessPacketResult ProcessPacket(Packet* pkt_in, Packet* pkt_out);
// Processes a general query packet
ErrorCode ProcessQueryPacket(const Packet* pkt_in, Packet* pkt_out);
// Formats a 'Stop-reply' packet, which is sent in response of a 'c'
// (continue), 's' (step) and '?' (query halt reason) commands.
void SetStopReply(Packet* pkt_out) const;
wasm_addr_t GetCurrentPc() const;
GdbServer* gdb_server_;
enum class Status { Running, Terminated };
std::atomic<Status> status_;
// Signal being processed.
int8_t cur_signal_;
Session* session_; // Session object not owned by the Target.
// Map used to make fast lookups when handling query packets.
typedef std::map<std::string, std::string> QueryPropertyMap;
QueryPropertyMap query_properties_;
DISALLOW_COPY_AND_ASSIGN(Target);
};
......
......@@ -263,7 +263,6 @@ bool SocketTransport::ReadSomeData() {
if (result == 0) {
return false; // The connection was gracefully closed.
}
// WSAEventSelect sets socket to non-blocking mode. This is essential
// for socket event notification to work, there is no workaround.
// See remarks section at the page
......
......@@ -124,6 +124,8 @@ class Transport : public TransportBase {
void Disconnect() override;
void Close() override;
static const int kBufSize = 4096;
protected:
// Copy buffered data to *dst up to len bytes and update dst and len.
void CopyFromBuffer(char** dst, int32_t* len);
......@@ -131,7 +133,6 @@ class Transport : public TransportBase {
// Read available data from the socket. Return false on EOF or error.
virtual bool ReadSomeData() = 0;
static const int kBufSize = 4096;
std::unique_ptr<char[]> buf_;
int32_t pos_;
int32_t size_;
......
......@@ -31,15 +31,17 @@ class JSArrayBuffer;
namespace wasm {
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
namespace gdb_server {
class GdbServer;
}
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
class AsyncCompileJob;
class ErrorThrower;
struct ModuleWireBytes;
class WasmFeatures;
namespace gdb_server {
class GdbServer;
}
class V8_EXPORT_PRIVATE CompilationResultResolver {
public:
virtual void OnCompilationSucceeded(Handle<WasmModuleObject> result) = 0;
......
......@@ -143,6 +143,25 @@ TEST_F(WasmGdbRemoteTest, GdbRemotePacketRunLengthEncoded) {
EXPECT_EQ("123333ab", std::string(packet2.GetPayload()));
}
TEST_F(WasmGdbRemoteTest, GdbRemoteUtilStringSplit) {
std::vector<std::string> parts1 = StringSplit({}, ",");
EXPECT_EQ(size_t(0), parts1.size());
auto parts2 = StringSplit("a", nullptr);
EXPECT_EQ(size_t(1), parts2.size());
EXPECT_EQ("a", parts2[0]);
auto parts3 = StringSplit(";a;bc;def;", ",");
EXPECT_EQ(size_t(1), parts3.size());
EXPECT_EQ(";a;bc;def;", parts3[0]);
auto parts4 = StringSplit(";a;bc;def;", ";");
EXPECT_EQ(size_t(3), parts4.size());
EXPECT_EQ("a", parts4[0]);
EXPECT_EQ("bc", parts4[1]);
EXPECT_EQ("def", parts4[2]);
}
class MockTransport : public TransportBase {
public:
MOCK_METHOD0(AcceptConnection, bool());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment