aboutsummaryrefslogtreecommitdiff
path: root/riscv/gdbserver.h
blob: 35e12b82b4e9614ec2f51232f635ef2d68bcf2d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#ifndef _RISCV_GDBSERVER_H
#define _RISCV_GDBSERVER_H

#include <stdint.h>

class sim_t;

template <typename T>
class circular_buffer_t
{
public:
  // The buffer can store capacity-1 data elements.
  circular_buffer_t(unsigned int capacity) : data(new T[capacity]),
      start(0), end(0), capacity(capacity) {}
  circular_buffer_t() : start(0), end(0), capacity(0) {}
  ~circular_buffer_t() { delete[] data; }

  T *data;
  unsigned int start;   // Data start, inclusive.
  unsigned int end;     // Data end, exclusive.
  unsigned int capacity;    // Size of the buffer.
  unsigned int size() const;
  bool empty() const { return start == end; }
  bool full() const { return ((end+1) % capacity) == start; }

  // Return size and address of the block of RAM where more data can be copied
  // to be added to the buffer.
  unsigned int contiguous_empty_size() const;
  T *contiguous_empty() { return data + end; }
  void data_added(unsigned int bytes);

  unsigned int contiguous_data_size() const;
  T *contiguous_data() { return data + start; }
  // Tell the buffer that some bytes were consumed from the start of the
  // buffer.
  void consume(unsigned int bytes);

  void reset();

  T operator[](unsigned int i) const { return data[(start + i) % capacity]; }

  void append(const T *src, unsigned int count);
};

// Class to track software breakpoints that we set.
class software_breakpoint_t
{
public:
  reg_t address;
  unsigned int size;
  uint32_t instruction;

  void insert(mmu_t* mmu);
  void remove(mmu_t* mmu);
};

class gdbserver_t;

class operation_t
{
  public:
    operation_t(gdbserver_t& gdbserver) : gs(gdbserver) {}
    virtual ~operation_t() {}

    // Called when the operation is first set as the current one.
    // Return true if this operation is complete. In that case the object will
    // be deleted.
    // Return false if more steps are required the next time the debug
    // interrupt is clear.
    virtual bool start() { return true; }

    // Perform the next step of this operation (which is probably to write to
    // Debug RAM and assert the debug interrupt).
    // Return true if this operation is complete. In that case the object will
    // be deleted.
    // Return false if more steps are required the next time the debug
    // interrupt is clear.
    virtual bool step() = 0;

    gdbserver_t& gs;
};

class gdbserver_t
{
public:
  // Create a new server, listening for connections from localhost on the given
  // port.
  gdbserver_t(uint16_t port, sim_t *sim);

  // Process all pending messages from a client.
  void handle();

  void handle_packet(const std::vector<uint8_t> &packet);
  void handle_interrupt();

  void handle_breakpoint(const std::vector<uint8_t> &packet);
  void handle_continue(const std::vector<uint8_t> &packet);
  void handle_extended(const std::vector<uint8_t> &packet);
  void handle_general_registers_read(const std::vector<uint8_t> &packet);
  void continue_general_registers_read();
  void handle_halt_reason(const std::vector<uint8_t> &packet);
  void handle_kill(const std::vector<uint8_t> &packet);
  void handle_memory_binary_write(const std::vector<uint8_t> &packet);
  void handle_memory_read(const std::vector<uint8_t> &packet);
  void handle_query(const std::vector<uint8_t> &packet);
  void handle_register_read(const std::vector<uint8_t> &packet);
  void continue_register_read();
  void handle_register_write(const std::vector<uint8_t> &packet);
  void handle_step(const std::vector<uint8_t> &packet);

  bool connected() const { return client_fd > 0; }

  // TODO: Move this into its own packet sending class?
  // Add the given message to send_buf.
  void send(const char* msg);
  // Hex-encode a 64-bit value, and send it to gcc in target byte order (little
  // endian).
  void send(uint64_t value);
  // Hex-encode a 32-bit value, and send it to gcc in target byte order (little
  // endian).
  void send(uint32_t value);
  void send_packet(const char* data);
  uint8_t running_checksum;
  // Send "$" and clear running checksum.
  void start_packet();
  // Send "#" and checksum.
  void end_packet(const char* data=NULL);

  // Write value to the index'th word in Debug RAM.
  void write_debug_ram(unsigned int index, uint32_t value);
  uint32_t read_debug_ram(unsigned int index);

  void set_interrupt(uint32_t hartid);

private:
  sim_t *sim;
  int socket_fd;
  int client_fd;
  circular_buffer_t<uint8_t> recv_buf;
  circular_buffer_t<uint8_t> send_buf;

  bool expect_ack;
  bool extended_mode;
  // Used to track whether we think the target is running. If we think it is
  // but it isn't, we need to tell gdb about it.
  bool running;

  std::map <reg_t, software_breakpoint_t> breakpoints;

  // Read pending data from the client.
  void read();
  void write();
  // Accept a new client if there isn't one already connected.
  void accept();
  // Process all complete requests in recv_buf.
  void process_requests();

  operation_t* operation;
  void set_operation(operation_t* operation);
};

#endif