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/session.h"
6#include "src/debug/wasm/gdb-server/packet.h"
7#include "src/debug/wasm/gdb-server/transport.h"
8
9namespace v8 {
10namespace internal {
11namespace wasm {
12namespace gdb_server {
13
14Session::Session(TransportBase* transport)
15    : io_(transport), connected_(true), ack_enabled_(true) {}
16
17void Session::WaitForDebugStubEvent() { io_->WaitForDebugStubEvent(); }
18
19bool Session::SignalThreadEvent() { return io_->SignalThreadEvent(); }
20
21bool Session::IsDataAvailable() const { return io_->IsDataAvailable(); }
22
23bool Session::IsConnected() const { return connected_; }
24
25void Session::Disconnect() {
26  io_->Disconnect();
27  connected_ = false;
28}
29
30bool Session::GetChar(char* ch) {
31  if (!io_->Read(ch, 1)) {
32    Disconnect();
33    return false;
34  }
35
36  return true;
37}
38
39bool Session::SendPacket(Packet* pkt, bool expect_ack) {
40  char ch;
41  do {
42    std::string data = pkt->GetPacketData();
43
44    TRACE_GDB_REMOTE("TX %s\n", data.size() < 160
45                                    ? data.c_str()
46                                    : (data.substr(0, 160) + "...").c_str());
47    if (!io_->Write(data.data(), static_cast<int32_t>(data.length()))) {
48      return false;
49    }
50
51    // If ACKs are off, we are done.
52    if (!expect_ack || !ack_enabled_) {
53      break;
54    }
55
56    // Otherwise, poll for '+'
57    if (!GetChar(&ch)) {
58      return false;
59    }
60
61    // Retry if we didn't get a '+'
62  } while (ch != '+');
63
64  return true;
65}
66
67bool Session::GetPayload(Packet* pkt, uint8_t* checksum) {
68  pkt->Clear();
69  *checksum = 0;
70
71  // Stream in the characters
72  char ch;
73  while (GetChar(&ch)) {
74    if (ch == '#') {
75      // If we see a '#' we must be done with the data.
76      return true;
77    } else if (ch == '$') {
78      // If we see a '$' we must have missed the last cmd, let's retry.
79      TRACE_GDB_REMOTE("RX Missing $, retry.\n");
80      *checksum = 0;
81      pkt->Clear();
82    } else {
83      // Keep a running XSUM.
84      *checksum += ch;
85      pkt->AddRawChar(ch);
86    }
87  }
88  return false;
89}
90
91bool Session::GetPacket(Packet* pkt) {
92  while (true) {
93    // Toss characters until we see a start of command
94    char ch;
95    do {
96      if (!GetChar(&ch)) {
97        return false;
98      }
99    } while (ch != '$');
100
101    uint8_t running_checksum = 0;
102    if (!GetPayload(pkt, &running_checksum)) {
103      return false;
104    }
105
106    // Get two nibble checksum
107    uint8_t trailing_checksum = 0;
108    char chars[2];
109    if (!GetChar(&chars[0]) || !GetChar(&chars[1]) ||
110        !HexToUInt8(chars, &trailing_checksum)) {
111      return false;
112    }
113
114    TRACE_GDB_REMOTE("RX $%s#%c%c\n", pkt->GetPayload(), chars[0], chars[1]);
115
116    pkt->ParseSequence();
117
118    // If ACKs are off, we are done.
119    if (!ack_enabled_) {
120      return true;
121    }
122
123    // If the XSUMs don't match, signal bad packet
124    if (trailing_checksum == running_checksum) {
125      char out[3] = {'+', 0, 0};
126
127      // If we have a sequence number
128      int32_t seq;
129      if (pkt->GetSequence(&seq)) {
130        // Respond with sequence number
131        UInt8ToHex(seq, &out[1]);
132        return io_->Write(out, 3);
133      } else {
134        return io_->Write(out, 1);
135      }
136    } else {
137      // Resend a bad XSUM and look for retransmit
138      TRACE_GDB_REMOTE("RX Bad XSUM, retry\n");
139      io_->Write("-", 1);
140      // retry...
141    }
142  }
143}
144
145}  // namespace gdb_server
146}  // namespace wasm
147}  // namespace internal
148}  // namespace v8
149