remove gdbstub
This commit is contained in:
21
external/gdbstub/LICENSE
vendored
21
external/gdbstub/LICENSE
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Stephen Lane-Walsh
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
553
external/gdbstub/gdbstub.h
vendored
553
external/gdbstub/gdbstub.h
vendored
@@ -1,553 +0,0 @@
|
||||
//
|
||||
// gdbstub version 1.1.1
|
||||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 Stephen Lane-Walsh
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#ifndef GDBSTUB_H
|
||||
#define GDBSTUB_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef void (*gdbstub_connected_t)(void * user_data);
|
||||
typedef void (*gdbstub_disconnected_t)(void * user_data);
|
||||
typedef void (*gdbstub_start_t)(void * user_data);
|
||||
typedef void (*gdbstub_stop_t)(void * user_data);
|
||||
typedef void (*gdbstub_step_t)(void * user_data);
|
||||
typedef void (*gdbstub_set_breakpoint_t)(void * user_data, uint32_t address);
|
||||
typedef void (*gdbstub_clear_breakpoint_t)(void * user_data, uint32_t address);
|
||||
typedef size_t (*gdbstub_get_memory_t)(void * user_data, char * buffer, size_t buffer_length, uint32_t address, size_t length);
|
||||
typedef size_t (*gdbstub_get_register_value_t)(void * user_data, char * buffer, size_t buffer_length, int reg);
|
||||
typedef size_t (*gdbstub_get_general_registers_t)(void * user_data, char * buffer, size_t buffer_length);
|
||||
|
||||
typedef struct gdbstub_config gdbstub_config_t;
|
||||
|
||||
struct gdbstub_config
|
||||
{
|
||||
uint16_t port;
|
||||
|
||||
void * user_data;
|
||||
|
||||
gdbstub_connected_t connected;
|
||||
|
||||
gdbstub_disconnected_t disconnected;
|
||||
|
||||
gdbstub_start_t start;
|
||||
|
||||
gdbstub_stop_t stop;
|
||||
|
||||
gdbstub_step_t step;
|
||||
|
||||
gdbstub_set_breakpoint_t set_breakpoint;
|
||||
|
||||
gdbstub_clear_breakpoint_t clear_breakpoint;
|
||||
|
||||
gdbstub_get_memory_t get_memory;
|
||||
|
||||
gdbstub_get_register_value_t get_register_value;
|
||||
|
||||
gdbstub_get_general_registers_t get_general_registers;
|
||||
|
||||
const char * target_config;
|
||||
size_t target_config_length;
|
||||
|
||||
const char * memory_map;
|
||||
size_t memory_map_length;
|
||||
};
|
||||
|
||||
typedef struct gdbstub gdbstub_t;
|
||||
|
||||
gdbstub_t * gdbstub_init(gdbstub_config_t config);
|
||||
|
||||
void gdbstub_term(gdbstub_t * gdb);
|
||||
|
||||
void gdbstub_tick(gdbstub_t * gdb);
|
||||
|
||||
void gdbstub_breakpoint_hit(gdbstub_t * gdb);
|
||||
|
||||
#endif // GDBSTUB_H
|
||||
|
||||
#ifdef GDBSTUB_IMPLEMENTATION
|
||||
|
||||
#include <cerrno>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment (lib, "Ws2_32.lib")
|
||||
#define close closesocket
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define GDBSTUB_BUFFER_LENGTH 4096
|
||||
#define GDBSTUB_PACKET_LENGTH 2048
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GDB_STATE_NO_PACKET,
|
||||
GDB_STATE_IN_PACKET,
|
||||
GDB_STATE_IN_CHECKSUM,
|
||||
|
||||
} gdbstate_t;
|
||||
|
||||
struct gdbstub
|
||||
{
|
||||
gdbstub_config_t config;
|
||||
|
||||
int server;
|
||||
int client;
|
||||
|
||||
char buffer[GDBSTUB_BUFFER_LENGTH];
|
||||
size_t buffer_length;
|
||||
|
||||
gdbstate_t state;
|
||||
char packet[GDBSTUB_BUFFER_LENGTH];
|
||||
size_t packet_length;
|
||||
uint8_t packet_checksum;
|
||||
|
||||
char checksum[2];
|
||||
size_t checksum_length;
|
||||
|
||||
};
|
||||
|
||||
void _gdbstub_recv(gdbstub_t * gdb);
|
||||
|
||||
void _gdbstub_send(gdbstub_t * gdb, const char * data, size_t data_length);
|
||||
|
||||
void _gdbstub_send_paged(gdbstub_t * gdb, int offset, int length, const char * data, size_t data_length);
|
||||
|
||||
void _gdbstub_process_packet(gdbstub_t * gdb);
|
||||
|
||||
gdbstub_t * gdbstub_init(gdbstub_config_t config)
|
||||
{
|
||||
gdbstub_t * gdb = (gdbstub_t *)malloc(sizeof(gdbstub_t));
|
||||
if (!gdb) {
|
||||
fprintf(stderr, "out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gdb->config = config;
|
||||
gdb->server = -1;
|
||||
gdb->client = -1;
|
||||
gdb->state = GDB_STATE_NO_PACKET;
|
||||
gdb->packet_length = 0;
|
||||
gdb->packet_checksum = 0;
|
||||
gdb->checksum_length = 0;
|
||||
|
||||
int result;
|
||||
struct sockaddr_in addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(gdb->config.port);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
gdb->server = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (gdb->server < 0) {
|
||||
perror("socket failed");
|
||||
free(gdb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = fcntl(gdb->server, F_SETFL, fcntl(gdb->server, F_GETFL, 0) | O_NONBLOCK);
|
||||
if (result < 0) {
|
||||
perror("fcntl O_NONBLOCK failed");
|
||||
free(gdb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
result = setsockopt(gdb->server, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
|
||||
if (result < 0) {
|
||||
perror("setsockopt SO_REUSEADDR failed");
|
||||
free(gdb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef SO_REUSEPORT
|
||||
result = setsockopt(gdb->server, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse));
|
||||
if (result < 0) {
|
||||
perror("setsockopt SO_REUSEPORT failed");
|
||||
free(gdb);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
result = bind(gdb->server, (struct sockaddr *)&addr, sizeof(addr));
|
||||
if (result < 0) {
|
||||
perror("bind failed");
|
||||
free(gdb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = listen(gdb->server, 1);
|
||||
if (result < 0) {
|
||||
perror("listen failed");
|
||||
free(gdb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
printf("listening for gdb on port %hu\n", gdb->config.port);
|
||||
return gdb;
|
||||
}
|
||||
|
||||
void gdbstub_term(gdbstub_t * gdb)
|
||||
{
|
||||
if (gdb) {
|
||||
if (gdb->client >= 0) {
|
||||
close(gdb->client);
|
||||
gdb->client = -1;
|
||||
}
|
||||
|
||||
if (gdb->server >= 0) {
|
||||
close(gdb->server);
|
||||
gdb->server = -1;
|
||||
}
|
||||
|
||||
free(gdb);
|
||||
}
|
||||
}
|
||||
|
||||
void gdbstub_tick(gdbstub_t * gdb)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
if (gdb->client < 0) {
|
||||
gdb->client = accept(gdb->server, (struct sockaddr *)&addr, &addrlen);
|
||||
if (gdb->client >= 0) {
|
||||
printf("accepted gdb connection\n");
|
||||
fcntl(gdb->client, F_SETFL, fcntl(gdb->client, F_GETFL, 0) | O_NONBLOCK);
|
||||
|
||||
if (gdb->config.connected) {
|
||||
gdb->config.connected(gdb->config.user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
_gdbstub_recv(gdb);
|
||||
}
|
||||
}
|
||||
|
||||
void gdbstub_breakpoint_hit(gdbstub_t * gdb) {
|
||||
_gdbstub_send(gdb, "T05", 3);
|
||||
}
|
||||
|
||||
void _gdbstub_send(gdbstub_t * gdb, const char * data, size_t data_length)
|
||||
{
|
||||
uint8_t checksum = 0;
|
||||
for (size_t i = 0; i < data_length; ++i) {
|
||||
checksum += data[i];
|
||||
}
|
||||
|
||||
gdb->packet_length = snprintf(gdb->packet, GDBSTUB_PACKET_LENGTH, "$%s#%02x", data, checksum);
|
||||
|
||||
int bytes = send(gdb->client, gdb->packet, gdb->packet_length, 0);
|
||||
|
||||
# if defined(GDBSTUB_DEBUG)
|
||||
printf("gdbstub sent '%s'\n", gdb->packet);
|
||||
# endif
|
||||
|
||||
if (bytes < 0) {
|
||||
perror("lost gdb connection");
|
||||
close(gdb->client);
|
||||
gdb->client = -1;
|
||||
gdb->state = GDB_STATE_NO_PACKET;
|
||||
|
||||
if (gdb->config.disconnected) {
|
||||
gdb->config.disconnected(gdb->config.user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _gdbstub_send_paged(gdbstub_t * gdb, int offset, int length, const char * data, size_t data_length)
|
||||
{
|
||||
if (length > GDBSTUB_PACKET_LENGTH - 6) {
|
||||
length = GDBSTUB_PACKET_LENGTH - 6;
|
||||
}
|
||||
|
||||
if (length < (data_length - offset)) {
|
||||
// Any page but the last
|
||||
gdb->buffer_length = snprintf(gdb->buffer, GDBSTUB_BUFFER_LENGTH, "m%.*s", length, data + offset);
|
||||
_gdbstub_send(gdb, gdb->buffer, gdb->buffer_length);
|
||||
}
|
||||
else if (offset != 0) {
|
||||
// The last page
|
||||
gdb->buffer_length = snprintf(gdb->buffer, GDBSTUB_BUFFER_LENGTH, "l%.*s", length, data + offset);
|
||||
_gdbstub_send(gdb, gdb->buffer, gdb->buffer_length);
|
||||
}
|
||||
else {
|
||||
// The only page
|
||||
gdb->buffer_length = snprintf(gdb->buffer, GDBSTUB_BUFFER_LENGTH, "l%s", data);
|
||||
_gdbstub_send(gdb, gdb->buffer, gdb->buffer_length);
|
||||
}
|
||||
}
|
||||
|
||||
void _gdbstub_recv(gdbstub_t * gdb)
|
||||
{
|
||||
gdb->buffer_length = recv(gdb->client, gdb->buffer, GDBSTUB_BUFFER_LENGTH, 0);
|
||||
if (gdb->buffer_length < 0 && errno != EAGAIN) {
|
||||
perror("lost gdb connection");
|
||||
close(gdb->client);
|
||||
gdb->client = -1;
|
||||
gdb->state = GDB_STATE_NO_PACKET;
|
||||
|
||||
if (gdb->config.disconnected) {
|
||||
gdb->config.disconnected(gdb->config.user_data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < gdb->buffer_length; ++i) {
|
||||
char c = gdb->buffer[i];
|
||||
|
||||
switch (gdb->state)
|
||||
{
|
||||
case GDB_STATE_NO_PACKET:
|
||||
if (c == '$') {
|
||||
gdb->state = GDB_STATE_IN_PACKET;
|
||||
gdb->packet_length = 0;
|
||||
gdb->packet_checksum = 0;
|
||||
}
|
||||
else if (c == 3) {
|
||||
// GDB Interrupt '0x03' Packet
|
||||
gdb->config.stop(gdb->config.user_data);
|
||||
_gdbstub_send(gdb, "S00", 3);
|
||||
}
|
||||
break;
|
||||
case GDB_STATE_IN_PACKET:
|
||||
if (c == '#') {
|
||||
gdb->state = GDB_STATE_IN_CHECKSUM;
|
||||
gdb->checksum_length = 0;
|
||||
}
|
||||
else {
|
||||
gdb->packet[gdb->packet_length++] = c;
|
||||
gdb->packet_checksum += c;
|
||||
}
|
||||
break;
|
||||
case GDB_STATE_IN_CHECKSUM:
|
||||
gdb->checksum[gdb->checksum_length++] = c;
|
||||
if (gdb->checksum_length == 2) {
|
||||
int checksum;
|
||||
sscanf(gdb->checksum, "%2x", &checksum);
|
||||
if (gdb->packet_checksum != checksum) {
|
||||
// freak out?
|
||||
}
|
||||
|
||||
send(gdb->client, "+", 1, 0);
|
||||
|
||||
gdb->packet[gdb->packet_length] = '\0';
|
||||
# if defined(GDBSTUB_DEBUG)
|
||||
printf("gdbstub received '$%s#%c%c'\n", gdb->packet, gdb->checksum[0], gdb->checksum[1]);
|
||||
# endif
|
||||
|
||||
_gdbstub_process_packet(gdb);
|
||||
|
||||
gdb->state = GDB_STATE_NO_PACKET;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _gdbstub_process_packet(gdbstub_t * gdb)
|
||||
{
|
||||
switch (gdb->packet[0])
|
||||
{
|
||||
case 'c':
|
||||
// Continue execution
|
||||
if (gdb->config.start) {
|
||||
gdb->config.start(gdb->config.user_data);
|
||||
}
|
||||
return;
|
||||
case 'D':
|
||||
// Disconnect
|
||||
printf("gdb disconnected\n");
|
||||
_gdbstub_send(gdb, "OK", 2);
|
||||
close(gdb->client);
|
||||
gdb->client = -1;
|
||||
gdb->state = GDB_STATE_NO_PACKET;
|
||||
|
||||
if (gdb->config.disconnected) {
|
||||
gdb->config.disconnected(gdb->config.user_data);
|
||||
}
|
||||
return;
|
||||
case 'g':
|
||||
// Get general registers
|
||||
if (gdb->config.get_general_registers) {
|
||||
gdb->buffer_length = gdb->config.get_general_registers(gdb->config.user_data, gdb->buffer, GDBSTUB_BUFFER_LENGTH);
|
||||
_gdbstub_send(gdb, gdb->buffer, gdb->buffer_length);
|
||||
}
|
||||
else {
|
||||
_gdbstub_send(gdb, "", 0);
|
||||
}
|
||||
return;
|
||||
case 'H':
|
||||
// Set active thread
|
||||
_gdbstub_send(gdb, "OK", 2);
|
||||
return;
|
||||
case 'm':
|
||||
// Read memory
|
||||
if (gdb->config.get_memory) {
|
||||
int address, length;
|
||||
sscanf(gdb->packet + 1, "%x,%x", &address, &length);
|
||||
gdb->buffer_length = gdb->config.get_memory(gdb->config.user_data, gdb->buffer, GDBSTUB_BUFFER_LENGTH, address, length);
|
||||
_gdbstub_send(gdb, gdb->buffer, gdb->buffer_length);
|
||||
}
|
||||
else {
|
||||
_gdbstub_send(gdb, "", 0);
|
||||
}
|
||||
return;
|
||||
case 'p':
|
||||
// Read the value of register n
|
||||
if (gdb->config.get_register_value) {
|
||||
int reg;
|
||||
sscanf(gdb->packet + 1, "%x", ®);
|
||||
gdb->buffer_length = gdb->config.get_register_value(gdb->config.user_data, gdb->buffer, GDBSTUB_BUFFER_LENGTH, reg);
|
||||
_gdbstub_send(gdb, gdb->buffer, gdb->buffer_length);
|
||||
}
|
||||
else {
|
||||
_gdbstub_send(gdb, "", 0);
|
||||
}
|
||||
return;
|
||||
case 'q':
|
||||
// Check for available features
|
||||
if (strncmp(gdb->packet, "qSupported", 10) == 0) {
|
||||
strcpy(gdb->buffer, "PacketSize=1024");
|
||||
gdb->buffer_length = 15;
|
||||
|
||||
if (gdb->config.target_config) {
|
||||
strcpy(gdb->buffer + gdb->buffer_length, ";qXfer:features:read+");
|
||||
gdb->buffer_length += 21;
|
||||
}
|
||||
|
||||
if (gdb->config.memory_map) {
|
||||
strcpy(gdb->buffer + gdb->buffer_length, ";qXfer:memory-map:read+");
|
||||
gdb->buffer_length += 23;
|
||||
}
|
||||
|
||||
_gdbstub_send(gdb, gdb->buffer, gdb->buffer_length);
|
||||
return;
|
||||
}
|
||||
// We have no thread ID
|
||||
else if (gdb->packet[1] == 'C') {
|
||||
_gdbstub_send(gdb, "QC00", 4);
|
||||
return;
|
||||
}
|
||||
// We are always "attached" to an existing process
|
||||
else if (strncmp(gdb->packet, "qAttached", 9) == 0) {
|
||||
_gdbstub_send(gdb, "1", 1);
|
||||
return;
|
||||
}
|
||||
// There is no trace running
|
||||
else if (strncmp(gdb->packet, "qTStatus", 8) == 0) {
|
||||
_gdbstub_send(gdb, "T0", 2);
|
||||
return;
|
||||
}
|
||||
// Target configuration XML
|
||||
else if (strncmp(gdb->packet, "qXfer:features:read:target.xml:", 31) == 0) {
|
||||
int offset, length;
|
||||
sscanf(gdb->packet + 31, "%x,%x", &offset, &length);
|
||||
_gdbstub_send_paged(gdb, offset, length, gdb->config.target_config, gdb->config.target_config_length);
|
||||
return;
|
||||
}
|
||||
// Memory map XML
|
||||
else if (strncmp(gdb->packet, "qXfer:memory-map:read::", 23) == 0) {
|
||||
int offset, length;
|
||||
sscanf(gdb->packet + 23, "%x,%x", &offset, &length);
|
||||
_gdbstub_send_paged(gdb, offset, length, gdb->config.memory_map, gdb->config.memory_map_length);
|
||||
return;
|
||||
}
|
||||
// Trace control operations
|
||||
else if (strncmp(gdb->packet, "qTfP", 4) == 0) {
|
||||
_gdbstub_send(gdb, "", 0);
|
||||
return;
|
||||
}
|
||||
else if (strncmp(gdb->packet, "qTfV", 4) == 0) {
|
||||
_gdbstub_send(gdb, "", 0);
|
||||
return;
|
||||
}
|
||||
else if (strncmp(gdb->packet, "qTsP", 4) == 0) {
|
||||
_gdbstub_send(gdb, "", 0);
|
||||
return;
|
||||
}
|
||||
// Thread #0
|
||||
else if (strncmp(gdb->packet, "qfThreadInfo", 12) == 0) {
|
||||
_gdbstub_send(gdb, "lm0", 3);
|
||||
return;
|
||||
}
|
||||
// GDB is ready to serve symbol requests
|
||||
else if (strncmp(gdb->packet, "qSymbol", 7) == 0) {
|
||||
_gdbstub_send(gdb, "OK", 2);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
// Single execution step
|
||||
if (gdb->config.step) {
|
||||
gdb->config.step(gdb->config.user_data);
|
||||
}
|
||||
_gdbstub_send(gdb, "T05", 3);
|
||||
return;
|
||||
case 'v':
|
||||
// Various remote operations, not supported
|
||||
_gdbstub_send(gdb, "", 0);
|
||||
return;
|
||||
case 'z':
|
||||
// Remove breakpoint
|
||||
if (gdb->config.clear_breakpoint) {
|
||||
uint32_t address;
|
||||
sscanf(gdb->packet, "z0,%x", &address);
|
||||
gdb->config.clear_breakpoint(gdb->config.user_data, address);
|
||||
}
|
||||
_gdbstub_send(gdb, "OK", 2);
|
||||
break;
|
||||
case 'Z':
|
||||
// Add breakpoint
|
||||
if (gdb->config.set_breakpoint) {
|
||||
uint32_t address;
|
||||
sscanf(gdb->packet, "Z0,%x", &address);
|
||||
gdb->config.set_breakpoint(gdb->config.user_data, address);
|
||||
}
|
||||
_gdbstub_send(gdb, "OK", 2);
|
||||
break;
|
||||
case '?':
|
||||
// Break immediately
|
||||
if (gdb->config.stop) {
|
||||
gdb->config.stop(gdb->config.user_data);
|
||||
}
|
||||
// TODO: improve
|
||||
_gdbstub_send(gdb, "S00", 3);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr, "unknown gdb command '%s'\n", gdb->packet);
|
||||
}
|
||||
|
||||
#endif // GDBSTUB_IMPLEMENTATION
|
||||
@@ -35,7 +35,6 @@ include_directories(
|
||||
../external/imgui/imgui
|
||||
../external/imgui/imgui/backends
|
||||
../external/discord-rpc/include
|
||||
../external/gdbstub
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
file(GLOB SOURCES *.cpp)
|
||||
file(GLOB HEADERS *.hpp)
|
||||
|
||||
add_library(backend ${SOURCES} ${HEADERS})
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(backend PUBLIC DISABLE_GDB_STUB)
|
||||
endif()
|
||||
add_library(backend ${SOURCES} ${HEADERS})
|
||||
@@ -1,293 +1 @@
|
||||
#include <Debugger.hpp>
|
||||
|
||||
#ifndef DISABLE_GDB_STUB
|
||||
#define GDBSTUB_IMPLEMENTATION
|
||||
#include <gdbstub.h>
|
||||
#include <log.hpp>
|
||||
#include <Core.hpp>
|
||||
|
||||
const char* target_xml =
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">"
|
||||
"<target version=\"1.0\">"
|
||||
"<architecture>mips:4000</architecture>"
|
||||
"<osabi>none</osabi>"
|
||||
"<feature name=\"org.gnu.gdb.mips.cpu\">"
|
||||
" <reg name=\"r0\" bitsize=\"64\" regnum=\"0\"/>"
|
||||
" <reg name=\"r1\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r2\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r3\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r4\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r5\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r6\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r7\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r8\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r9\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r10\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r11\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r12\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r13\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r14\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r15\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r16\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r17\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r18\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r19\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r20\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r21\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r22\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r23\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r24\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r25\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r26\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r27\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r28\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r29\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r30\" bitsize=\"64\"/>"
|
||||
" <reg name=\"r31\" bitsize=\"64\"/>"
|
||||
" <reg name=\"lo\" bitsize=\"64\" regnum=\"33\"/>"
|
||||
" <reg name=\"hi\" bitsize=\"64\" regnum=\"34\"/>"
|
||||
" <reg name=\"pc\" bitsize=\"64\" regnum=\"37\"/>"
|
||||
" </feature>"
|
||||
"<feature name=\"org.gnu.gdb.mips.cp0\">"
|
||||
" <reg name=\"status\" bitsize=\"32\" regnum=\"32\"/>"
|
||||
" <reg name=\"badvaddr\" bitsize=\"32\" regnum=\"35\"/>"
|
||||
" <reg name=\"cause\" bitsize=\"32\" regnum=\"36\"/>"
|
||||
" </feature>"
|
||||
"<!-- TODO fix the sizes here. How do we deal with configurable sizes? -->"
|
||||
"<feature name=\"org.gnu.gdb.mips.fpu\">"
|
||||
" <reg name=\"f0\" bitsize=\"32\" type=\"ieee_single\" regnum=\"38\"/>"
|
||||
" <reg name=\"f1\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f2\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f3\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f4\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f5\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f6\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f7\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f8\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f9\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f10\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f11\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f12\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f13\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f14\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f15\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f16\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f17\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f18\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f19\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f20\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f21\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f22\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f23\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f24\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f25\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f26\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f27\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f28\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f29\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f30\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"f31\" bitsize=\"32\" type=\"ieee_single\"/>"
|
||||
" <reg name=\"fcsr\" bitsize=\"32\" group=\"float\"/>"
|
||||
" <reg name=\"fir\" bitsize=\"32\" group=\"float\"/>"
|
||||
" </feature>"
|
||||
"</target>";
|
||||
|
||||
const char* memory_map =
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<memory-map>"
|
||||
"<!-- KUSEG - TLB mapped, treat it as a giant block of RAM. Not ideal, but not sure how else to deal with it -->"
|
||||
"<memory type=\"ram\" start=\"0x0000000000000000\" length=\"0x80000000\"/>"
|
||||
|
||||
"<!-- KSEG0 hardware mapped, full copy of the memory map goes here -->" // TODO finish
|
||||
"<memory type=\"ram\" start=\"0xffffffff80000000\" length=\"0x800000\"/>" // RDRAM
|
||||
"<memory type=\"ram\" start=\"0xffffffff84000000\" length=\"0x1000\"/>" // RSP DMEM
|
||||
"<memory type=\"ram\" start=\"0xffffffff84001000\" length=\"0x1000\"/>" // RSP IMEM
|
||||
"<memory type=\"rom\" start=\"0xffffffff9fc00000\" length=\"0x7c0\"/>" // PIF ROM
|
||||
|
||||
"<!-- KSEG1 hardware mapped, full copy of the memory map goes here -->" // TODO finish
|
||||
"<memory type=\"ram\" start=\"0xffffffffa0000000\" length=\"0x800000\"/>" // RDRAM
|
||||
"<memory type=\"ram\" start=\"0xffffffffa4000000\" length=\"0x1000\"/>" // RSP DMEM
|
||||
"<memory type=\"ram\" start=\"0xffffffffa4001000\" length=\"0x1000\"/>" // RSP IMEM
|
||||
"<memory type=\"rom\" start=\"0xffffffffbfc00000\" length=\"0x7c0\"/>" // PIF ROM
|
||||
"</memory-map>";
|
||||
|
||||
void debugStart(void* user_data) {
|
||||
auto* debugger = (Debugger*)user_data;
|
||||
debugger->broken = false;
|
||||
}
|
||||
|
||||
void debugStop(void* user_data) {
|
||||
auto* debugger = (Debugger*)user_data;
|
||||
debugger->broken = true;
|
||||
}
|
||||
|
||||
void debugStep(void* user_data) {
|
||||
auto* debugger = (Debugger*)user_data;
|
||||
bool old_broken = debugger->broken;
|
||||
debugger->broken = false;
|
||||
n64::Core::CpuStep(debugger->core);
|
||||
debugger->broken = old_broken;
|
||||
debugger->steps += 2;
|
||||
}
|
||||
|
||||
void debugSetBreakpoint(void* user_data, u32 address) {
|
||||
auto* debugger = (Debugger*)user_data;
|
||||
auto* breakpoint = (Breakpoint*)malloc(sizeof(Breakpoint));
|
||||
breakpoint->address = address;
|
||||
breakpoint->next = nullptr;
|
||||
|
||||
// Special case for this being the first breakpoint
|
||||
if (debugger->breakpoints == nullptr) {
|
||||
debugger->breakpoints = breakpoint;
|
||||
} else {
|
||||
// Find end of the list
|
||||
auto* tail = debugger->breakpoints;
|
||||
while (tail->next != nullptr) {
|
||||
tail = tail->next;
|
||||
}
|
||||
|
||||
tail->next = breakpoint;
|
||||
}
|
||||
}
|
||||
|
||||
void debugClearBreakpoint(void* user_data, u32 address) {
|
||||
auto* debugger = (Debugger*)user_data;
|
||||
if (debugger->breakpoints == nullptr) {
|
||||
return; // No breakpoints set at all
|
||||
} else if (debugger->breakpoints->address == address) {
|
||||
// Special case for the first breakpoint being the one we want to clear
|
||||
auto* next = debugger->breakpoints->next;
|
||||
free(debugger->breakpoints);
|
||||
debugger->breakpoints = next;
|
||||
} else {
|
||||
// Find the breakpoint somewhere in the list and free it
|
||||
auto* iter = debugger->breakpoints;
|
||||
while (iter->next != nullptr) {
|
||||
if (iter->next->address == address) {
|
||||
auto* next = iter->next->next;
|
||||
free(iter->next);
|
||||
iter->next = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t debugGetMemory(void* user_data, char* buffer, size_t length, u32 address, size_t bytes) {
|
||||
auto* debugger = (Debugger*)user_data;
|
||||
printf("Checking memory at address 0x%08X\n", address);
|
||||
int printed = 0;
|
||||
u32 paddr;
|
||||
if(!n64::MapVAddr(debugger->core.CpuGetRegs(), n64::LOAD, address, paddr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < bytes; i++) {
|
||||
u8 value = debugger->core.mem.Read8(debugger->core.CpuGetRegs(), paddr + i);
|
||||
printed += snprintf(buffer + (i*2), length, "%02X", value);
|
||||
}
|
||||
printf("Get memory: %ld bytes from 0x%08X: %d\n", bytes, paddr, printed);
|
||||
return printed + 1;
|
||||
}
|
||||
|
||||
size_t debugGetRegisterValue(void* user_data, char * buffer, size_t buffer_length, int reg) {
|
||||
auto* debugger = (Debugger*)user_data;
|
||||
switch (reg) {
|
||||
case 0 ... 31:
|
||||
return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().gpr[reg]);
|
||||
case 32:
|
||||
return snprintf(buffer, buffer_length, "%08x", debugger->core.CpuGetRegs().cop0.status.raw);
|
||||
case 33:
|
||||
return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().lo);
|
||||
case 34:
|
||||
return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().hi);
|
||||
case 35:
|
||||
return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().cop0.badVaddr);
|
||||
case 36:
|
||||
return snprintf(buffer, buffer_length, "%08x", debugger->core.CpuGetRegs().cop0.cause.raw);
|
||||
case 37:
|
||||
//printf("Sending PC: 0x%016lX\n", debugger->core.CpuGetRegs().pc);
|
||||
return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().pc);
|
||||
case 38 ... 71: // TODO FPU stuff
|
||||
return snprintf(buffer, buffer_length, "%08x", 0);
|
||||
default:
|
||||
Util::panic("Debug get register %d value\n", reg);
|
||||
}
|
||||
}
|
||||
|
||||
size_t debugGetGeneralRegisters(void* user_data, char * buffer, size_t buffer_length) {
|
||||
auto* debugger = (Debugger*)user_data;
|
||||
printf("The buffer length is %ld!\n", buffer_length);
|
||||
size_t printed = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int ofs = i * 16; // 64 bit regs take up 16 ascii chars to print in hex
|
||||
if (ofs + 16 > buffer_length) {
|
||||
Util::panic("Too big!\n");
|
||||
}
|
||||
u64 reg = debugger->core.CpuGetRegs().gpr[i];
|
||||
printed += snprintf(buffer + ofs, buffer_length - ofs, "%016lx", reg);
|
||||
}
|
||||
return printed;
|
||||
}
|
||||
|
||||
Debugger::Debugger(n64::Core& core) : core(core) {
|
||||
gdbstub_config_t config;
|
||||
memset(&config, 0, sizeof(gdbstub_config_t));
|
||||
config.port = 1337;
|
||||
config.user_data = this;
|
||||
config.start = (gdbstub_start_t) debugStart;
|
||||
config.stop = (gdbstub_stop_t) debugStop;
|
||||
config.step = (gdbstub_step_t) debugStep;
|
||||
config.set_breakpoint = (gdbstub_set_breakpoint_t) debugSetBreakpoint;
|
||||
config.clear_breakpoint = (gdbstub_clear_breakpoint_t) debugClearBreakpoint;
|
||||
config.get_memory = (gdbstub_get_memory_t) debugGetMemory;
|
||||
config.get_register_value = (gdbstub_get_register_value_t) debugGetRegisterValue;
|
||||
config.get_general_registers = (gdbstub_get_general_registers_t) debugGetGeneralRegisters;
|
||||
|
||||
config.target_config = target_xml;
|
||||
config.target_config_length = strlen(target_xml);
|
||||
|
||||
printf("Sizeof target: %ld\n", config.target_config_length);
|
||||
|
||||
config.memory_map = memory_map;
|
||||
config.memory_map_length = strlen(memory_map);
|
||||
|
||||
printf("Sizeof memory map: %ld\n", config.memory_map_length);
|
||||
|
||||
gdb = gdbstub_init(config);
|
||||
if (!gdb) {
|
||||
Util::panic("Failed to initialize GDB stub!\n");
|
||||
}
|
||||
}
|
||||
|
||||
Debugger::~Debugger() {
|
||||
if(enabled) {
|
||||
gdbstub_term(gdb);
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::tick() const {
|
||||
gdbstub_tick(gdb);
|
||||
}
|
||||
|
||||
void Debugger::breakpointHit() {
|
||||
broken = true;
|
||||
gdbstub_breakpoint_hit(gdb);
|
||||
}
|
||||
#else
|
||||
Debugger::Debugger(n64::Core& core) {
|
||||
|
||||
}
|
||||
|
||||
Debugger::~Debugger() {
|
||||
|
||||
}
|
||||
|
||||
void Debugger::tick() const {
|
||||
|
||||
}
|
||||
|
||||
void Debugger::breakpointHit() {
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
#include <common.hpp>
|
||||
#include <gdbstub.h>
|
||||
|
||||
struct Breakpoint {
|
||||
u32 address = 0;
|
||||
@@ -10,13 +9,10 @@ struct Breakpoint {
|
||||
namespace n64 { struct Core; }
|
||||
|
||||
struct Debugger {
|
||||
Debugger(n64::Core& core);
|
||||
~Debugger();
|
||||
Debugger(n64::Core& core) :core(core) {}
|
||||
~Debugger() = default;
|
||||
|
||||
bool broken = false, enabled = true;
|
||||
#ifndef DISABLE_GDB_STUB
|
||||
int steps = 0;
|
||||
gdbstub_t* gdb;
|
||||
Breakpoint* breakpoints = nullptr;
|
||||
n64::Core& core;
|
||||
|
||||
@@ -30,9 +26,7 @@ struct Debugger {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
inline bool checkBreakpoint(u32 address) const { return false; }
|
||||
#endif
|
||||
void tick() const;
|
||||
void breakpointHit();
|
||||
|
||||
void tick() const {}
|
||||
void breakpointHit() {}
|
||||
};
|
||||
Reference in New Issue
Block a user