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