1// Copyright 2020 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/debug/wasm/gdb-server/target.h" 6 7#include <inttypes.h> 8#include "src/base/platform/time.h" 9#include "src/debug/wasm/gdb-server/gdb-remote-util.h" 10#include "src/debug/wasm/gdb-server/gdb-server.h" 11#include "src/debug/wasm/gdb-server/packet.h" 12#include "src/debug/wasm/gdb-server/session.h" 13#include "src/debug/wasm/gdb-server/transport.h" 14 15namespace v8 { 16namespace internal { 17namespace wasm { 18namespace gdb_server { 19 20static const int kThreadId = 1; 21 22// Signals. 23static const int kSigTrace = 5; 24static const int kSigSegv = 11; 25 26Target::Target(GdbServer* gdb_server) 27 : gdb_server_(gdb_server), 28 status_(Status::Running), 29 cur_signal_(0), 30 session_(nullptr), 31 debugger_initial_suspension_(true), 32 semaphore_(0), 33 current_isolate_(nullptr) { 34 InitQueryPropertyMap(); 35} 36 37void Target::InitQueryPropertyMap() { 38 // Request LLDB to send packets up to 4000 bytes for bulk transfers. 39 query_properties_["Supported"] = 40 "PacketSize=1000;vContSupported-;qXfer:libraries:read+;wasm+;"; 41 42 query_properties_["Attached"] = "1"; 43 44 // There is only one register, named 'pc', in this architecture 45 query_properties_["RegisterInfo0"] = 46 "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:" 47 "General Purpose Registers;gcc:16;dwarf:16;generic:pc;"; 48 query_properties_["RegisterInfo1"] = "E45"; 49 50 // ProcessInfo for wasm32 51 query_properties_["ProcessInfo"] = 52 "pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:6c6c6462;triple:" + 53 Mem2Hex("wasm32-unknown-unknown-wasm") + ";ptrsize:4;"; 54 query_properties_["Symbol"] = "OK"; 55 56 // Current thread info 57 char buff[16]; 58 snprintf(buff, sizeof(buff), "QC%x", kThreadId); 59 query_properties_["C"] = buff; 60} 61 62void Target::Terminate() { 63 // Executed in the Isolate thread, when the process shuts down. 64 SetStatus(Status::Terminated); 65} 66 67void Target::OnProgramBreak(Isolate* isolate, 68 const std::vector<wasm_addr_t>& call_frames) { 69 OnSuspended(isolate, kSigTrace, call_frames); 70} 71void Target::OnException(Isolate* isolate, 72 const std::vector<wasm_addr_t>& call_frames) { 73 OnSuspended(isolate, kSigSegv, call_frames); 74} 75void Target::OnSuspended(Isolate* isolate, int signal, 76 const std::vector<wasm_addr_t>& call_frames) { 77 // This function will be called in the isolate thread, when the wasm 78 // interpreter gets suspended. 79 80 bool isWaitingForSuspension = (status_ == Status::WaitingForSuspension); 81 SetStatus(Status::Suspended, signal, call_frames, isolate); 82 if (isWaitingForSuspension) { 83 // Wake the GdbServer thread that was blocked waiting for the Target 84 // to suspend. 85 semaphore_.Signal(); 86 } else if (session_) { 87 session_->SignalThreadEvent(); 88 } 89} 90 91void Target::Run(Session* session) { 92 // Executed in the GdbServer thread. 93 session_ = session; 94 do { 95 WaitForDebugEvent(); 96 ProcessDebugEvent(); 97 ProcessCommands(); 98 } while (!IsTerminated() && session_->IsConnected()); 99 session_ = nullptr; 100} 101 102void Target::WaitForDebugEvent() { 103 // Executed in the GdbServer thread. 104 105 if (status_ == Status::Running) { 106 // Wait for either: 107 // * the thread to fault (or single-step) 108 // * an interrupt from LLDB 109 session_->WaitForDebugStubEvent(); 110 } 111} 112 113void Target::ProcessDebugEvent() { 114 // Executed in the GdbServer thread 115 116 if (status_ == Status::Running) { 117 // Blocks, waiting for the engine to suspend. 118 Suspend(); 119 } 120 121 // Here, the wasm interpreter has suspended and we have updated the current 122 // thread info. 123 124 if (debugger_initial_suspension_) { 125 // First time on a connection, we don't send the signal. 126 // All other times, send the signal that triggered us. 127 debugger_initial_suspension_ = false; 128 } else { 129 Packet pktOut; 130 SetStopReply(&pktOut); 131 session_->SendPacket(&pktOut, false); 132 } 133} 134 135void Target::Suspend() { 136 // Executed in the GdbServer thread 137 if (status_ == Status::Running) { 138 // TODO(paolosev) - this only suspends the wasm interpreter. 139 gdb_server_->Suspend(); 140 141 status_ = Status::WaitingForSuspension; 142 } 143 144 while (status_ == Status::WaitingForSuspension) { 145 if (semaphore_.WaitFor(base::TimeDelta::FromMilliseconds(500))) { 146 // Here the wasm interpreter is suspended. 147 return; 148 } 149 } 150} 151 152void Target::ProcessCommands() { 153 // GDB-remote messages are processed in the GDBServer thread. 154 155 if (IsTerminated()) { 156 return; 157 } else if (status_ != Status::Suspended) { 158 // Don't process commands if we haven't stopped. 159 return; 160 } 161 162 // Now we are ready to process commands. 163 // Loop through packets until we process a continue packet or a detach. 164 Packet recv, reply; 165 while (session_->IsConnected()) { 166 if (!session_->GetPacket(&recv)) { 167 continue; 168 } 169 170 reply.Clear(); 171 ProcessPacketResult result = ProcessPacket(&recv, &reply); 172 switch (result) { 173 case ProcessPacketResult::Paused: 174 session_->SendPacket(&reply); 175 break; 176 177 case ProcessPacketResult::Continue: 178 DCHECK_EQ(status_, Status::Running); 179 // If this is a continue type command, break out of this loop. 180 gdb_server_->QuitMessageLoopOnPause(); 181 return; 182 183 case ProcessPacketResult::Detach: 184 SetStatus(Status::Running); 185 session_->SendPacket(&reply); 186 session_->Disconnect(); 187 gdb_server_->QuitMessageLoopOnPause(); 188 return; 189 190 case ProcessPacketResult::Kill: 191 session_->SendPacket(&reply); 192 exit(-9); 193 194 default: 195 UNREACHABLE(); 196 } 197 } 198 199 if (!session_->IsConnected()) { 200 debugger_initial_suspension_ = true; 201 } 202} 203 204Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in, 205 Packet* pkt_out) { 206 ErrorCode err = ErrorCode::None; 207 208 // Clear the outbound message. 209 pkt_out->Clear(); 210 211 // Set the sequence number, if present. 212 int32_t seq = -1; 213 if (pkt_in->GetSequence(&seq)) { 214 pkt_out->SetSequence(seq); 215 } 216 217 // A GDB-remote packet begins with an upper- or lower-case letter, which 218 // generally represents a single command. 219 // The letters 'q' and 'Q' introduce a "General query packets" and are used 220 // to extend the protocol with custom commands. 221 // The format of GDB-remote commands is documented here: 222 // https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview. 223 char cmd; 224 pkt_in->GetRawChar(&cmd); 225 226 switch (cmd) { 227 // Queries the reason the target halted. 228 // IN : $? 229 // OUT: A Stop-reply packet 230 case '?': 231 SetStopReply(pkt_out); 232 break; 233 234 // Resumes execution 235 // IN : $c 236 // OUT: A Stop-reply packet is sent later, when the execution halts. 237 case 'c': 238 SetStatus(Status::Running); 239 return ProcessPacketResult::Continue; 240 241 // Detaches the debugger from this target 242 // IN : $D 243 // OUT: $OK 244 case 'D': 245 TRACE_GDB_REMOTE("Requested Detach.\n"); 246 pkt_out->AddString("OK"); 247 return ProcessPacketResult::Detach; 248 249 // Read general registers (We only support register 'pc' that contains 250 // the current instruction pointer). 251 // IN : $g 252 // OUT: $xx...xx 253 case 'g': { 254 uint64_t pc = GetCurrentPc(); 255 pkt_out->AddBlock(&pc, sizeof(pc)); 256 break; 257 } 258 259 // Write general registers - NOT SUPPORTED 260 // IN : $Gxx..xx 261 // OUT: $ (empty string) 262 case 'G': { 263 break; 264 } 265 266 // Set thread for subsequent operations. For Wasm targets, we currently 267 // assume that there is only one thread with id = kThreadId (= 1). 268 // IN : $H(c/g)(-1,0,xxxx) 269 // OUT: $OK 270 case 'H': { 271 // Type of the operation (‘m’, ‘M’, ‘g’, ‘G’, ...) 272 char operation; 273 if (!pkt_in->GetRawChar(&operation)) { 274 err = ErrorCode::BadFormat; 275 break; 276 } 277 278 uint64_t thread_id; 279 if (!pkt_in->GetNumberSep(&thread_id, 0)) { 280 err = ErrorCode::BadFormat; 281 break; 282 } 283 284 // Ignore, only one thread supported for now. 285 pkt_out->AddString("OK"); 286 break; 287 } 288 289 // Kills the debuggee. 290 // IN : $k 291 // OUT: $OK 292 case 'k': 293 TRACE_GDB_REMOTE("Requested Kill.\n"); 294 pkt_out->AddString("OK"); 295 return ProcessPacketResult::Kill; 296 297 // Reads {llll} addressable memory units starting at address {aaaa}. 298 // IN : $maaaa,llll 299 // OUT: $xx..xx 300 case 'm': { 301 uint64_t address; 302 if (!pkt_in->GetNumberSep(&address, 0)) { 303 err = ErrorCode::BadFormat; 304 break; 305 } 306 wasm_addr_t wasm_addr(address); 307 308 uint64_t len; 309 if (!pkt_in->GetNumberSep(&len, 0)) { 310 err = ErrorCode::BadFormat; 311 break; 312 } 313 314 if (len > Transport::kBufSize / 2) { 315 err = ErrorCode::BadArgs; 316 break; 317 } 318 319 uint32_t length = static_cast<uint32_t>(len); 320 uint8_t buff[Transport::kBufSize]; 321 if (wasm_addr.ModuleId() > 0) { 322 uint32_t read = 323 gdb_server_->GetWasmModuleBytes(wasm_addr, buff, length); 324 if (read > 0) { 325 pkt_out->AddBlock(buff, read); 326 } else { 327 err = ErrorCode::Failed; 328 } 329 } else { 330 err = ErrorCode::BadArgs; 331 } 332 break; 333 } 334 335 // Writes {llll} addressable memory units starting at address {aaaa}. 336 // IN : $Maaaa,llll:xx..xx 337 // OUT: $OK 338 case 'M': { 339 // Writing to memory not supported for Wasm. 340 err = ErrorCode::Failed; 341 break; 342 } 343 344 // pN: Reads the value of register N. 345 // IN : $pxx 346 // OUT: $xx..xx 347 case 'p': { 348 uint64_t pc = GetCurrentPc(); 349 pkt_out->AddBlock(&pc, sizeof(pc)); 350 } break; 351 352 case 'q': { 353 err = ProcessQueryPacket(pkt_in, pkt_out); 354 break; 355 } 356 357 // Single step 358 // IN : $s 359 // OUT: A Stop-reply packet is sent later, when the execution halts. 360 case 's': { 361 if (status_ == Status::Suspended) { 362 gdb_server_->PrepareStep(); 363 SetStatus(Status::Running); 364 } 365 return ProcessPacketResult::Continue; 366 } 367 368 // Find out if the thread 'id' is alive. 369 // IN : $T 370 // OUT: $OK if alive, $Enn if thread is dead. 371 case 'T': { 372 uint64_t id; 373 if (!pkt_in->GetNumberSep(&id, 0)) { 374 err = ErrorCode::BadFormat; 375 break; 376 } 377 if (id != kThreadId) { 378 err = ErrorCode::BadArgs; 379 break; 380 } 381 pkt_out->AddString("OK"); 382 break; 383 } 384 385 // Z: Adds a breakpoint 386 // IN : $Z<type>,<addr>,<kind> 387 // <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint 388 // OUT: $OK (success) or $Enn (error) 389 case 'Z': { 390 uint64_t breakpoint_type; 391 uint64_t breakpoint_address; 392 uint64_t breakpoint_kind; 393 // Only software breakpoints are supported. 394 if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 || 395 !pkt_in->GetNumberSep(&breakpoint_address, 0) || 396 !pkt_in->GetNumberSep(&breakpoint_kind, 0)) { 397 err = ErrorCode::BadFormat; 398 break; 399 } 400 401 wasm_addr_t wasm_breakpoint_addr(breakpoint_address); 402 if (!gdb_server_->AddBreakpoint(wasm_breakpoint_addr.ModuleId(), 403 wasm_breakpoint_addr.Offset())) { 404 err = ErrorCode::Failed; 405 break; 406 } 407 408 pkt_out->AddString("OK"); 409 break; 410 } 411 412 // z: Removes a breakpoint 413 // IN : $z<type>,<addr>,<kind> 414 // <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint 415 // OUT: $OK (success) or $Enn (error) 416 case 'z': { 417 uint64_t breakpoint_type; 418 uint64_t breakpoint_address; 419 uint64_t breakpoint_kind; 420 if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 || 421 !pkt_in->GetNumberSep(&breakpoint_address, 0) || 422 !pkt_in->GetNumberSep(&breakpoint_kind, 0)) { 423 err = ErrorCode::BadFormat; 424 break; 425 } 426 427 wasm_addr_t wasm_breakpoint_addr(breakpoint_address); 428 if (!gdb_server_->RemoveBreakpoint(wasm_breakpoint_addr.ModuleId(), 429 wasm_breakpoint_addr.Offset())) { 430 err = ErrorCode::Failed; 431 break; 432 } 433 434 pkt_out->AddString("OK"); 435 break; 436 } 437 438 // If the command is not recognized, ignore it by sending an empty reply. 439 default: { 440 TRACE_GDB_REMOTE("Unknown command: %s\n", pkt_in->GetPayload()); 441 } 442 } 443 444 // If there is an error, return the error code instead of a payload 445 if (err != ErrorCode::None) { 446 pkt_out->Clear(); 447 pkt_out->AddRawChar('E'); 448 pkt_out->AddWord8(static_cast<uint8_t>(err)); 449 } 450 return ProcessPacketResult::Paused; 451} 452 453Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in, 454 Packet* pkt_out) { 455 const char* str = &pkt_in->GetPayload()[1]; 456 457 // Get first thread query 458 // IN : $qfThreadInfo 459 // OUT: $m<tid> 460 // 461 // Get next thread query 462 // IN : $qsThreadInfo 463 // OUT: $m<tid> or l to denote end of list. 464 if (!strcmp(str, "fThreadInfo") || !strcmp(str, "sThreadInfo")) { 465 if (str[0] == 'f') { 466 pkt_out->AddString("m"); 467 pkt_out->AddNumberSep(kThreadId, 0); 468 } else { 469 pkt_out->AddString("l"); 470 } 471 return ErrorCode::None; 472 } 473 474 // Get a list of loaded libraries 475 // IN : $qXfer:libraries:read 476 // OUT: an XML document which lists loaded libraries, with this format: 477 // <library-list> 478 // <library name="foo.wasm"> 479 // <section address="0x100000000"/> 480 // </library> 481 // <library name="bar.wasm"> 482 // <section address="0x200000000"/> 483 // </library> 484 // </library-list> 485 // Note that LLDB must be compiled with libxml2 support to handle this packet. 486 std::string tmp = "Xfer:libraries:read"; 487 if (!strncmp(str, tmp.data(), tmp.length())) { 488 std::vector<GdbServer::WasmModuleInfo> modules = 489 gdb_server_->GetLoadedModules(true); 490 std::string result("l<library-list>"); 491 for (const auto& module : modules) { 492 wasm_addr_t address(module.module_id, 0); 493 char address_string[32]; 494 snprintf(address_string, sizeof(address_string), "%" PRIu64, 495 static_cast<uint64_t>(address)); 496 result += "<library name=\""; 497 result += module.module_name; 498 result += "\"><section address=\""; 499 result += address_string; 500 result += "\"/></library>"; 501 } 502 result += "</library-list>"; 503 pkt_out->AddString(result.c_str()); 504 return ErrorCode::None; 505 } 506 507 // Get the current call stack. 508 // IN : $qWasmCallStack 509 // OUT: $xx..xxyy..yyzz..zz (A sequence of uint64_t values represented as 510 // consecutive 8-bytes blocks). 511 std::vector<std::string> toks = StringSplit(str, ":;"); 512 if (toks[0] == "WasmCallStack") { 513 std::vector<wasm_addr_t> call_stack_pcs = gdb_server_->GetWasmCallStack(); 514 std::vector<uint64_t> buffer; 515 for (wasm_addr_t pc : call_stack_pcs) { 516 buffer.push_back(pc); 517 } 518 pkt_out->AddBlock(buffer.data(), 519 static_cast<uint32_t>(sizeof(uint64_t) * buffer.size())); 520 return ErrorCode::None; 521 } 522 523 // Get a Wasm global value in the Wasm module specified. 524 // IN : $qWasmGlobal:frame_index;index 525 // OUT: $xx..xx 526 if (toks[0] == "WasmGlobal") { 527 if (toks.size() == 3) { 528 uint32_t frame_index = 529 static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10)); 530 uint32_t index = 531 static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10)); 532 uint8_t buff[16]; 533 uint32_t size = 0; 534 if (gdb_server_->GetWasmGlobal(frame_index, index, buff, 16, &size)) { 535 pkt_out->AddBlock(buff, size); 536 return ErrorCode::None; 537 } else { 538 return ErrorCode::Failed; 539 } 540 } 541 return ErrorCode::BadFormat; 542 } 543 544 // Get a Wasm local value in the stack frame specified. 545 // IN : $qWasmLocal:frame_index;index 546 // OUT: $xx..xx 547 if (toks[0] == "WasmLocal") { 548 if (toks.size() == 3) { 549 uint32_t frame_index = 550 static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10)); 551 uint32_t index = 552 static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10)); 553 uint8_t buff[16]; 554 uint32_t size = 0; 555 if (gdb_server_->GetWasmLocal(frame_index, index, buff, 16, &size)) { 556 pkt_out->AddBlock(buff, size); 557 return ErrorCode::None; 558 } else { 559 return ErrorCode::Failed; 560 } 561 } 562 return ErrorCode::BadFormat; 563 } 564 565 // Get a Wasm local from the operand stack at the index specified. 566 // IN : qWasmStackValue:frame_index;index 567 // OUT: $xx..xx 568 if (toks[0] == "WasmStackValue") { 569 if (toks.size() == 3) { 570 uint32_t frame_index = 571 static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10)); 572 uint32_t index = 573 static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10)); 574 uint8_t buff[16]; 575 uint32_t size = 0; 576 if (gdb_server_->GetWasmStackValue(frame_index, index, buff, 16, &size)) { 577 pkt_out->AddBlock(buff, size); 578 return ErrorCode::None; 579 } else { 580 return ErrorCode::Failed; 581 } 582 } 583 return ErrorCode::BadFormat; 584 } 585 586 // Read Wasm Memory. 587 // IN : $qWasmMem:module_id;addr;len 588 // OUT: $xx..xx 589 if (toks[0] == "WasmMem") { 590 if (toks.size() == 4) { 591 uint32_t module_id = strtoul(toks[1].data(), nullptr, 10); 592 uint32_t address = strtoul(toks[2].data(), nullptr, 16); 593 uint32_t length = strtoul(toks[3].data(), nullptr, 16); 594 if (length > Transport::kBufSize / 2) { 595 return ErrorCode::BadArgs; 596 } 597 uint8_t buff[Transport::kBufSize]; 598 uint32_t read = 599 gdb_server_->GetWasmMemory(module_id, address, buff, length); 600 if (read > 0) { 601 pkt_out->AddBlock(buff, read); 602 return ErrorCode::None; 603 } else { 604 return ErrorCode::Failed; 605 } 606 } 607 return ErrorCode::BadFormat; 608 } 609 610 // Read Wasm Data. 611 // IN : $qWasmData:module_id;addr;len 612 // OUT: $xx..xx 613 if (toks[0] == "WasmData") { 614 if (toks.size() == 4) { 615 uint32_t module_id = strtoul(toks[1].data(), nullptr, 10); 616 uint32_t address = strtoul(toks[2].data(), nullptr, 16); 617 uint32_t length = strtoul(toks[3].data(), nullptr, 16); 618 if (length > Transport::kBufSize / 2) { 619 return ErrorCode::BadArgs; 620 } 621 uint8_t buff[Transport::kBufSize]; 622 uint32_t read = 623 gdb_server_->GetWasmData(module_id, address, buff, length); 624 if (read > 0) { 625 pkt_out->AddBlock(buff, read); 626 return ErrorCode::None; 627 } else { 628 return ErrorCode::Failed; 629 } 630 } 631 return ErrorCode::BadFormat; 632 } 633 634 // No match so far, check the property cache. 635 QueryPropertyMap::const_iterator it = query_properties_.find(toks[0]); 636 if (it != query_properties_.end()) { 637 pkt_out->AddString(it->second.data()); 638 } 639 // If not found, just send an empty response. 640 return ErrorCode::None; 641} 642 643// A Stop-reply packet has the format: 644// Sxx 645// or: 646// Txx<name1>:<value1>;...;<nameN>:<valueN> 647// where 'xx' is a two-digit hex number that represents the stop signal 648// and the <name>:<value> pairs are used to report additional information, 649// like the thread id. 650void Target::SetStopReply(Packet* pkt_out) const { 651 pkt_out->AddRawChar('T'); 652 pkt_out->AddWord8(cur_signal_); 653 654 // Adds 'thread-pcs:<pc1>,...,<pcN>;' A list of pc values for all threads that 655 // currently exist in the process. 656 char buff[64]; 657 snprintf(buff, sizeof(buff), "thread-pcs:%" PRIx64 ";", 658 static_cast<uint64_t>(GetCurrentPc())); 659 pkt_out->AddString(buff); 660 661 // Adds 'thread:<tid>;' pair. Note that a terminating ';' is required. 662 pkt_out->AddString("thread:"); 663 pkt_out->AddNumberSep(kThreadId, ';'); 664 665 // If the loaded modules have changed since the last stop packet, signals 666 // that. 667 if (gdb_server_->HasModuleListChanged()) pkt_out->AddString("library:;"); 668} 669 670void Target::SetStatus(Status status, int8_t signal, 671 std::vector<wasm_addr_t> call_frames, Isolate* isolate) { 672 v8::base::MutexGuard guard(&mutex_); 673 674 DCHECK((status == Status::Suspended && signal != 0 && 675 call_frames.size() > 0 && isolate != nullptr) || 676 (status != Status::Suspended && signal == 0 && 677 call_frames.size() == 0 && isolate == nullptr)); 678 679 current_isolate_ = isolate; 680 status_ = status; 681 cur_signal_ = signal; 682 call_frames_ = call_frames; 683} 684 685const std::vector<wasm_addr_t> Target::GetCallStack() const { 686 v8::base::MutexGuard guard(&mutex_); 687 688 return call_frames_; 689} 690 691wasm_addr_t Target::GetCurrentPc() const { 692 v8::base::MutexGuard guard(&mutex_); 693 694 wasm_addr_t pc{0}; 695 if (call_frames_.size() > 0) { 696 pc = call_frames_[0]; 697 } 698 return pc; 699} 700 701} // namespace gdb_server 702} // namespace wasm 703} // namespace internal 704} // namespace v8 705