#include "src/debug/wasm/gdb-server/target.h"
#include <inttypes.h>
#include "src/base/platform/time.h"
#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
#include "src/debug/wasm/gdb-server/gdb-server.h"
#include "src/debug/wasm/gdb-server/packet.h"
#include "src/debug/wasm/gdb-server/session.h"
#include "src/debug/wasm/gdb-server/transport.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace gdb_server {
static const int kThreadId = 1;
static const int kSigTrace = 5;
static const int kSigSegv = 11;
Target::Target(GdbServer* gdb_server)
: gdb_server_(gdb_server),
status_(Status::Running),
cur_signal_(0),
session_(nullptr),
debugger_initial_suspension_(true),
semaphore_(0),
current_isolate_(nullptr) {
InitQueryPropertyMap();
}
void Target::InitQueryPropertyMap() {
query_properties_["Supported"] =
"PacketSize=1000;vContSupported-;qXfer:libraries:read+;wasm+;";
query_properties_["Attached"] = "1";
query_properties_["RegisterInfo0"] =
"name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:"
"General Purpose Registers;gcc:16;dwarf:16;generic:pc;";
query_properties_["RegisterInfo1"] = "E45";
query_properties_["ProcessInfo"] =
"pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:6c6c6462;triple:" +
Mem2Hex("wasm32-unknown-unknown-wasm") + ";ptrsize:4;";
query_properties_["Symbol"] = "OK";
char buff[16];
snprintf(buff, sizeof(buff), "QC%x", kThreadId);
query_properties_["C"] = buff;
}
void Target::Terminate() {
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) {
bool isWaitingForSuspension = (status_ == Status::WaitingForSuspension);
SetStatus(Status::Suspended, signal, call_frames, isolate);
if (isWaitingForSuspension) {
semaphore_.Signal();
} else if (session_) {
session_->SignalThreadEvent();
}
}
void Target::Run(Session* session) {
session_ = session;
do {
WaitForDebugEvent();
ProcessDebugEvent();
ProcessCommands();
} while (!IsTerminated() && session_->IsConnected());
session_ = nullptr;
}
void Target::WaitForDebugEvent() {
if (status_ == Status::Running) {
session_->WaitForDebugStubEvent();
}
}
void Target::ProcessDebugEvent() {
if (status_ == Status::Running) {
Suspend();
}
if (debugger_initial_suspension_) {
debugger_initial_suspension_ = false;
} else {
Packet pktOut;
SetStopReply(&pktOut);
session_->SendPacket(&pktOut, false);
}
}
void Target::Suspend() {
if (status_ == Status::Running) {
gdb_server_->Suspend();
status_ = Status::WaitingForSuspension;
}
while (status_ == Status::WaitingForSuspension) {
if (semaphore_.WaitFor(base::TimeDelta::FromMilliseconds(500))) {
return;
}
}
}
void Target::ProcessCommands() {
if (IsTerminated()) {
return;
} else if (status_ != Status::Suspended) {
return;
}
Packet recv, reply;
while (session_->IsConnected()) {
if (!session_->GetPacket(&recv)) {
continue;
}
reply.Clear();
ProcessPacketResult result = ProcessPacket(&recv, &reply);
switch (result) {
case ProcessPacketResult::Paused:
session_->SendPacket(&reply);
break;
case ProcessPacketResult::Continue:
DCHECK_EQ(status_, Status::Running);
gdb_server_->QuitMessageLoopOnPause();
return;
case ProcessPacketResult::Detach:
SetStatus(Status::Running);
session_->SendPacket(&reply);
session_->Disconnect();
gdb_server_->QuitMessageLoopOnPause();
return;
case ProcessPacketResult::Kill:
session_->SendPacket(&reply);
exit(-9);
default:
UNREACHABLE();
}
}
if (!session_->IsConnected()) {
debugger_initial_suspension_ = true;
}
}
Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
Packet* pkt_out) {
ErrorCode err = ErrorCode::None;
pkt_out->Clear();
int32_t seq = -1;
if (pkt_in->GetSequence(&seq)) {
pkt_out->SetSequence(seq);
}
char cmd;
pkt_in->GetRawChar(&cmd);
switch (cmd) {
case '?':
SetStopReply(pkt_out);
break;
case 'c':
SetStatus(Status::Running);
return ProcessPacketResult::Continue;
case 'D':
TRACE_GDB_REMOTE("Requested Detach.\n");
pkt_out->AddString("OK");
return ProcessPacketResult::Detach;
case 'g': {
uint64_t pc = GetCurrentPc();
pkt_out->AddBlock(&pc, sizeof(pc));
break;
}
case 'G': {
break;
}
case 'H': {
char operation;
if (!pkt_in->GetRawChar(&operation)) {
err = ErrorCode::BadFormat;
break;
}
uint64_t thread_id;
if (!pkt_in->GetNumberSep(&thread_id, 0)) {
err = ErrorCode::BadFormat;
break;
}
pkt_out->AddString("OK");
break;
}
case 'k':
TRACE_GDB_REMOTE("Requested Kill.\n");
pkt_out->AddString("OK");
return ProcessPacketResult::Kill;
case 'm': {
uint64_t address;
if (!pkt_in->GetNumberSep(&address, 0)) {
err = ErrorCode::BadFormat;
break;
}
wasm_addr_t wasm_addr(address);
uint64_t len;
if (!pkt_in->GetNumberSep(&len, 0)) {
err = ErrorCode::BadFormat;
break;
}
if (len > Transport::kBufSize / 2) {
err = ErrorCode::BadArgs;
break;
}
uint32_t length = static_cast<uint32_t>(len);
uint8_t buff[Transport::kBufSize];
if (wasm_addr.ModuleId() > 0) {
uint32_t read =
gdb_server_->GetWasmModuleBytes(wasm_addr, buff, length);
if (read > 0) {
pkt_out->AddBlock(buff, read);
} else {
err = ErrorCode::Failed;
}
} else {
uint32_t read = gdb_server_->GetWasmMemory(
gdb_server_->GetFirstModuleId(), wasm_addr.Offset(), buff, length);
if (read > 0) {
pkt_out->AddBlock(buff, read);
} else {
err = ErrorCode::Failed;
}
}
break;
}
case 'M': {
err = ErrorCode::Failed;
break;
}
case 'p': {
uint64_t pc = GetCurrentPc();
pkt_out->AddBlock(&pc, sizeof(pc));
} break;
case 'q': {
err = ProcessQueryPacket(pkt_in, pkt_out);
break;
}
case 's': {
if (status_ == Status::Suspended) {
gdb_server_->PrepareStep();
SetStatus(Status::Running);
}
return ProcessPacketResult::Continue;
}
case 'T': {
uint64_t id;
if (!pkt_in->GetNumberSep(&id, 0)) {
err = ErrorCode::BadFormat;
break;
}
if (id != kThreadId) {
err = ErrorCode::BadArgs;
break;
}
pkt_out->AddString("OK");
break;
}
case 'Z': {
uint64_t breakpoint_type;
uint64_t breakpoint_address;
uint64_t breakpoint_kind;
if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
!pkt_in->GetNumberSep(&breakpoint_address, 0) ||
!pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
err = ErrorCode::BadFormat;
break;
}
wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
if (!gdb_server_->AddBreakpoint(wasm_breakpoint_addr.ModuleId(),
wasm_breakpoint_addr.Offset())) {
err = ErrorCode::Failed;
break;
}
pkt_out->AddString("OK");
break;
}
case 'z': {
uint64_t breakpoint_type;
uint64_t breakpoint_address;
uint64_t breakpoint_kind;
if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
!pkt_in->GetNumberSep(&breakpoint_address, 0) ||
!pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
err = ErrorCode::BadFormat;
break;
}
wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
if (!gdb_server_->RemoveBreakpoint(wasm_breakpoint_addr.ModuleId(),
wasm_breakpoint_addr.Offset())) {
err = ErrorCode::Failed;
break;
}
pkt_out->AddString("OK");
break;
}
default: {
TRACE_GDB_REMOTE("Unknown command: %s\n", pkt_in->GetPayload());
}
}
if (err != ErrorCode::None) {
pkt_out->Clear();
pkt_out->AddRawChar('E');
pkt_out->AddWord8(static_cast<uint8_t>(err));
}
return ProcessPacketResult::Paused;
}
Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
Packet* pkt_out) {
const char* str = &pkt_in->GetPayload()[1];
if (!strcmp(str, "fThreadInfo") || !strcmp(str, "sThreadInfo")) {
if (str[0] == 'f') {
pkt_out->AddString("m");
pkt_out->AddNumberSep(kThreadId, 0);
} else {
pkt_out->AddString("l");
}
return ErrorCode::None;
}
std::string tmp = "Xfer:libraries:read";
if (!strncmp(str, tmp.data(), tmp.length())) {
std::vector<GdbServer::WasmModuleInfo> modules =
gdb_server_->GetLoadedModules(true);
std::string result("l<library-list>");
for (const auto& module : modules) {
wasm_addr_t address(module.module_id, 0);
char address_string[32];
snprintf(address_string, sizeof(address_string), "%" PRIu64,
static_cast<uint64_t>(address));
result += "<library name=\"";
result += module.module_name;
result += "\"><section address=\"";
result += address_string;
result += "\"/></library>";
}
result += "</library-list>";
pkt_out->AddString(result.c_str());
return ErrorCode::None;
}
std::vector<std::string> toks = StringSplit(str, ":;");
if (toks[0] == "WasmCallStack") {
std::vector<wasm_addr_t> call_stack_pcs = gdb_server_->GetWasmCallStack();
std::vector<uint64_t> buffer;
for (wasm_addr_t pc : call_stack_pcs) {
buffer.push_back(pc);
}
pkt_out->AddBlock(buffer.data(),
static_cast<uint32_t>(sizeof(uint64_t) * buffer.size()));
return ErrorCode::None;
}
if (toks[0] == "WasmGlobal") {
if (toks.size() == 3) {
uint32_t frame_index =
static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
uint32_t index =
static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
uint8_t buff[16];
uint32_t size = 0;
if (gdb_server_->GetWasmGlobal(frame_index, index, buff, 16, &size)) {
pkt_out->AddBlock(buff, size);
return ErrorCode::None;
} else {
return ErrorCode::Failed;
}
}
return ErrorCode::BadFormat;
}
if (toks[0] == "WasmLocal") {
if (toks.size() == 3) {
uint32_t frame_index =
static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
uint32_t index =
static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
uint8_t buff[16];
uint32_t size = 0;
if (gdb_server_->GetWasmLocal(frame_index, index, buff, 16, &size)) {
pkt_out->AddBlock(buff, size);
return ErrorCode::None;
} else {
return ErrorCode::Failed;
}
}
return ErrorCode::BadFormat;
}
if (toks[0] == "WasmStackValue") {
if (toks.size() == 3) {
uint32_t frame_index =
static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
uint32_t index =
static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
uint8_t buff[16];
uint32_t size = 0;
if (gdb_server_->GetWasmStackValue(frame_index, index, buff, 16, &size)) {
pkt_out->AddBlock(buff, size);
return ErrorCode::None;
} else {
return ErrorCode::Failed;
}
}
return ErrorCode::BadFormat;
}
if (toks[0] == "WasmMem") {
if (toks.size() == 4) {
uint32_t module_id =
static_cast<uint32_t>(strtoul(toks[1].data(), nullptr, 10));
uint32_t address =
static_cast<uint32_t>(strtoul(toks[2].data(), nullptr, 16));
uint32_t length =
static_cast<uint32_t>(strtoul(toks[3].data(), nullptr, 16));
if (length > Transport::kBufSize / 2) {
return ErrorCode::BadArgs;
}
uint8_t buff[Transport::kBufSize];
uint32_t read =
gdb_server_->GetWasmMemory(module_id, address, buff, length);
if (read > 0) {
pkt_out->AddBlock(buff, read);
return ErrorCode::None;
} else {
return ErrorCode::Failed;
}
}
return ErrorCode::BadFormat;
}
if (toks[0] == "WasmData") {
if (toks.size() == 4) {
uint32_t module_id =
static_cast<uint32_t>(strtoul(toks[1].data(), nullptr, 10));
uint32_t address =
static_cast<uint32_t>(strtoul(toks[2].data(), nullptr, 16));
uint32_t length =
static_cast<uint32_t>(strtoul(toks[3].data(), nullptr, 16));
if (length > Transport::kBufSize / 2) {
return ErrorCode::BadArgs;
}
uint8_t buff[Transport::kBufSize];
uint32_t read =
gdb_server_->GetWasmData(module_id, address, buff, length);
if (read > 0) {
pkt_out->AddBlock(buff, read);
return ErrorCode::None;
} else {
return ErrorCode::Failed;
}
}
return ErrorCode::BadFormat;
}
QueryPropertyMap::const_iterator it = query_properties_.find(toks[0]);
if (it != query_properties_.end()) {
pkt_out->AddString(it->second.data());
}
return ErrorCode::None;
}
void Target::SetStopReply(Packet* pkt_out) const {
pkt_out->AddRawChar('T');
pkt_out->AddWord8(cur_signal_);
char buff[64];
snprintf(buff, sizeof(buff), "thread-pcs:%" PRIx64 ";",
static_cast<uint64_t>(GetCurrentPc()));
pkt_out->AddString(buff);
pkt_out->AddString("thread:");
pkt_out->AddNumberSep(kThreadId, ';');
if (gdb_server_->HasModuleListChanged()) pkt_out->AddString("library:;");
}
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;
}
}
}
}
}