Add fonts + gdb stub WIP
This commit is contained in:
546
external/gdbstub.h
vendored
Normal file
546
external/gdbstub.h
vendored
Normal file
@@ -0,0 +1,546 @@
|
|||||||
|
//
|
||||||
|
// 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 <stdint.h>
|
||||||
|
#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 ssize_t (*gdbstub_get_memory_t)(void * user_data, char * buffer, size_t buffer_length, uint32_t address, size_t length);
|
||||||
|
typedef ssize_t (*gdbstub_get_register_value_t)(void * user_data, char * buffer, size_t buffer_length, int reg);
|
||||||
|
typedef ssize_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 <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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>
|
||||||
|
#include <netinet/in.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];
|
||||||
|
ssize_t buffer_length;
|
||||||
|
|
||||||
|
gdbstate_t state;
|
||||||
|
char packet[GDBSTUB_BUFFER_LENGTH];
|
||||||
|
ssize_t packet_length;
|
||||||
|
uint8_t packet_checksum;
|
||||||
|
|
||||||
|
char checksum[2];
|
||||||
|
ssize_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 (ssize_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) {
|
||||||
|
// TODO: investigate
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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
|
||||||
1
external/parallel-rdp/CMakeLists.txt
vendored
1
external/parallel-rdp/CMakeLists.txt
vendored
@@ -64,6 +64,7 @@ target_include_directories(parallel-rdp PUBLIC
|
|||||||
parallel-rdp-standalone
|
parallel-rdp-standalone
|
||||||
..
|
..
|
||||||
../../src/frontend
|
../../src/frontend
|
||||||
|
../../src/frontend/imgui
|
||||||
../../src
|
../../src
|
||||||
../imgui
|
../imgui
|
||||||
../imgui/imgui
|
../imgui/imgui
|
||||||
|
|||||||
3
external/parallel-rdp/ParallelRDPWrapper.cpp
vendored
3
external/parallel-rdp/ParallelRDPWrapper.cpp
vendored
@@ -253,8 +253,9 @@ void UpdateScreen(n64::Core& core, Window& imguiWindow, Util::IntrusivePtr<Image
|
|||||||
Util::IntrusivePtr<CommandBuffer> cmd = wsi->get_device().request_command_buffer();
|
Util::IntrusivePtr<CommandBuffer> cmd = wsi->get_device().request_command_buffer();
|
||||||
|
|
||||||
cmd->begin_render_pass(wsi->get_device().get_swapchain_render_pass(SwapchainRenderPass::ColorOnly));
|
cmd->begin_render_pass(wsi->get_device().get_swapchain_render_pass(SwapchainRenderPass::ColorOnly));
|
||||||
|
DrawFullscreenTexturedQuad(image, cmd);
|
||||||
|
|
||||||
ImGui_ImplVulkan_RenderDrawData(imguiWindow.Present(image, core), cmd->get_command_buffer());
|
ImGui_ImplVulkan_RenderDrawData(imguiWindow.Present(core), cmd->get_command_buffer());
|
||||||
|
|
||||||
cmd->end_render_pass();
|
cmd->end_render_pass();
|
||||||
wsi->get_device().submit(cmd);
|
wsi->get_device().submit(cmd);
|
||||||
|
|||||||
BIN
resources/OpenSans.ttf
Normal file
BIN
resources/OpenSans.ttf
Normal file
Binary file not shown.
BIN
resources/Sweet16.ttf
Normal file
BIN
resources/Sweet16.ttf
Normal file
Binary file not shown.
@@ -9,7 +9,9 @@ find_package(fmt REQUIRED)
|
|||||||
|
|
||||||
add_library(frontend-imgui STATIC
|
add_library(frontend-imgui STATIC
|
||||||
Window.cpp
|
Window.cpp
|
||||||
Window.hpp)
|
Window.hpp
|
||||||
|
widgets.cpp
|
||||||
|
debugger.hpp debugger.cpp)
|
||||||
|
|
||||||
target_include_directories(frontend-imgui PUBLIC
|
target_include_directories(frontend-imgui PUBLIC
|
||||||
.
|
.
|
||||||
@@ -24,4 +26,4 @@ target_include_directories(frontend-imgui PUBLIC
|
|||||||
../../../external/parallel-rdp/parallel-rdp-standalone/vulkan
|
../../../external/parallel-rdp/parallel-rdp-standalone/vulkan
|
||||||
../../../external/parallel-rdp/parallel-rdp-standalone/util
|
../../../external/parallel-rdp/parallel-rdp-standalone/util
|
||||||
../../../external/parallel-rdp/parallel-rdp-standalone/volk)
|
../../../external/parallel-rdp/parallel-rdp-standalone/volk)
|
||||||
target_link_libraries(frontend-imgui PUBLIC SDL2main SDL2 imgui nfd fmt)
|
target_link_libraries(frontend-imgui PUBLIC ws2_32 SDL2main SDL2 imgui nfd fmt)
|
||||||
@@ -48,12 +48,9 @@ void Window::InitImgui() {
|
|||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
(void)io;
|
(void)io;
|
||||||
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
|
||||||
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
|
||||||
|
|
||||||
// Setup Dear ImGui style
|
// Setup Dear ImGui style
|
||||||
ImGui::StyleColorsDark();
|
ImGui::StyleColorsDark();
|
||||||
//ImGui::StyleColorsLight();
|
|
||||||
|
|
||||||
instance = GetVkInstance();
|
instance = GetVkInstance();
|
||||||
physicalDevice = GetVkPhysicalDevice();
|
physicalDevice = GetVkPhysicalDevice();
|
||||||
@@ -110,50 +107,19 @@ void Window::InitImgui() {
|
|||||||
initInfo.CheckVkResultFn = check_vk_result;
|
initInfo.CheckVkResultFn = check_vk_result;
|
||||||
ImGui_ImplVulkan_Init(&initInfo, GetVkRenderPass());
|
ImGui_ImplVulkan_Init(&initInfo, GetVkRenderPass());
|
||||||
|
|
||||||
// Load Fonts
|
uiFont = io.Fonts->AddFontFromFileTTF("resources/OpenSans.ttf", 15.f);
|
||||||
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
|
codeFont = io.Fonts->AddFontFromFileTTF("resources/Sweet16.ttf", 15.f);
|
||||||
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
|
|
||||||
// - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
|
|
||||||
// - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
|
|
||||||
// - Read 'docs/FONTS.md' for more instructions and details.
|
|
||||||
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
|
|
||||||
//io.Fonts->AddFontDefault();
|
|
||||||
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
|
|
||||||
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
|
|
||||||
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
|
|
||||||
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
|
|
||||||
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
|
|
||||||
//IM_ASSERT(font != NULL);
|
|
||||||
io.Fonts->AddFontDefault();
|
|
||||||
|
|
||||||
// Upload Fonts
|
|
||||||
{
|
{
|
||||||
VkCommandBuffer commandBuffer = GetVkCommandBuffer();
|
VkCommandBuffer commandBuffer = GetVkCommandBuffer();
|
||||||
ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
|
ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
|
||||||
SubmitRequestedVkCommandBuffer();
|
SubmitRequestedVkCommandBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
VkSamplerCreateInfo samplerCreateInfo {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
|
||||||
.magFilter = VK_FILTER_NEAREST,
|
|
||||||
.minFilter = VK_FILTER_NEAREST,
|
|
||||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
|
|
||||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
|
||||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
|
||||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
|
||||||
.maxAnisotropy = 1.0f,
|
|
||||||
.minLod = -1000,
|
|
||||||
.maxLod = 1000,
|
|
||||||
};
|
|
||||||
VkSampler sampler;
|
|
||||||
err = vkCreateSampler(device, &samplerCreateInfo, allocator, &sampler);
|
|
||||||
check_vk_result(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::~Window() {
|
Window::~Window() {
|
||||||
VkResult err = vkDeviceWaitIdle(device);
|
VkResult err = vkDeviceWaitIdle(device);
|
||||||
check_vk_result(err);
|
check_vk_result(err);
|
||||||
vkDestroySampler(device, screenSampler, nullptr);
|
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
ImGui_ImplSDL2_Shutdown();
|
ImGui_ImplSDL2_Shutdown();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
@@ -162,73 +128,17 @@ Window::~Window() {
|
|||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImDrawData* Window::Present(const Util::IntrusivePtr<Vulkan::Image>& image, n64::Core& core) {
|
ImDrawData* Window::Present(n64::Core& core) {
|
||||||
ImGui_ImplVulkan_NewFrame();
|
ImGui_ImplVulkan_NewFrame();
|
||||||
ImGui_ImplSDL2_NewFrame(window);
|
ImGui_ImplSDL2_NewFrame(window);
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
Render(image, core);
|
Render(core);
|
||||||
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
return ImGui::GetDrawData();
|
return ImGui::GetDrawData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::Render(const Util::IntrusivePtr<Vulkan::Image>& image, n64::Core& core) {
|
void Window::Render(n64::Core& core) {
|
||||||
if(windowID == SDL_GetWindowID(SDL_GetMouseFocus())) {
|
MainMenuBar(core);
|
||||||
ImGui::BeginMainMenuBar();
|
|
||||||
if (ImGui::BeginMenu("File")) {
|
|
||||||
if (ImGui::MenuItem("Open", "O")) {
|
|
||||||
nfdchar_t *outpath;
|
|
||||||
const nfdu8filteritem_t filter{"Nintendo 64 roms", "n64,z64,v64,N64,Z64,V64"};
|
|
||||||
nfdresult_t result = NFD_OpenDialog(&outpath, &filter, 1, nullptr);
|
|
||||||
if (result == NFD_OKAY) {
|
|
||||||
core.LoadROM(outpath);
|
|
||||||
NFD_FreePath(outpath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Exit")) {
|
|
||||||
core.done = true;
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
if (ImGui::BeginMenu("Emulation")) {
|
|
||||||
if (ImGui::MenuItem("Reset")) {
|
|
||||||
core.Reset();
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Stop")) {
|
|
||||||
core.Stop();
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem(core.pause ? "Resume" : "Pause", nullptr, false, core.romLoaded)) {
|
|
||||||
core.TogglePause();
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
ImGui::EndMainMenuBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Begin("Screen");
|
|
||||||
if(core.romLoaded && !core.pause) {
|
|
||||||
auto size = ImGui::GetContentRegionAvail();
|
|
||||||
float current_aspect_ratio = size.x / size.y;
|
|
||||||
if (ASPECT_RATIO > current_aspect_ratio) {
|
|
||||||
size.y = size.x / ASPECT_RATIO;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
size.x = size.y * ASPECT_RATIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetCursorPos({
|
|
||||||
(ImGui::GetContentRegionAvail().x / 2) - (size.x / 2),
|
|
||||||
(ImGui::GetContentRegionAvail().y / 2) - (size.y / 2) + 24
|
|
||||||
});
|
|
||||||
ImGui::Image(
|
|
||||||
ImGui_ImplVulkan_AddTexture(
|
|
||||||
screenSampler,
|
|
||||||
image->get_view().get_view(),
|
|
||||||
image->get_layout(VK_IMAGE_LAYOUT_GENERAL)
|
|
||||||
),
|
|
||||||
size
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,20 @@
|
|||||||
struct Window {
|
struct Window {
|
||||||
explicit Window(n64::Core& core);
|
explicit Window(n64::Core& core);
|
||||||
~Window();
|
~Window();
|
||||||
ImDrawData* Present(const Util::IntrusivePtr<Vulkan::Image>&, n64::Core& core);
|
ImDrawData* Present(n64::Core& core);
|
||||||
|
|
||||||
[[nodiscard]] bool gotClosed(SDL_Event event);
|
[[nodiscard]] bool gotClosed(SDL_Event event);
|
||||||
|
ImFont *uiFont, *codeFont;
|
||||||
|
u32 windowID;
|
||||||
private:
|
private:
|
||||||
SDL_Window* window;
|
SDL_Window* window;
|
||||||
u32 windowID;
|
|
||||||
void InitSDL();
|
void InitSDL();
|
||||||
void InitImgui();
|
void InitImgui();
|
||||||
void Render(const Util::IntrusivePtr<Vulkan::Image>&, n64::Core& core);
|
void Render(n64::Core& core);
|
||||||
|
void MainMenuBar(n64::Core& core);
|
||||||
|
void DebuggerWindow(n64::Core& core);
|
||||||
|
|
||||||
VkPhysicalDevice physicalDevice{};
|
VkPhysicalDevice physicalDevice{};
|
||||||
VkSampler screenSampler{};
|
|
||||||
VkDescriptorSet screenTexture{};
|
|
||||||
VkDevice device{};
|
VkDevice device{};
|
||||||
uint32_t queueFamily{uint32_t(-1)};
|
uint32_t queueFamily{uint32_t(-1)};
|
||||||
VkQueue queue{};
|
VkQueue queue{};
|
||||||
|
|||||||
115
src/frontend/imgui/debugger.cpp
Normal file
115
src/frontend/imgui/debugger.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include <Core.hpp>
|
||||||
|
#include <debugger.hpp>
|
||||||
|
|
||||||
|
void DebugStart(void* user_data) {
|
||||||
|
auto* core = (n64::Core*)user_data;
|
||||||
|
core->debuggerState.broken = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugStop(void* user_data) {
|
||||||
|
auto* core = (n64::Core*)user_data;
|
||||||
|
core->debuggerState.broken = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugStep(void* user_data) {
|
||||||
|
auto* core = (n64::Core*)user_data;
|
||||||
|
bool old_broken = core->debuggerState.broken;
|
||||||
|
core->debuggerState.broken = false;
|
||||||
|
core->Step();
|
||||||
|
core->debuggerState.broken = old_broken;
|
||||||
|
core->debuggerState.steps += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSetBreakpoint(void* user_data, u32 address) {
|
||||||
|
auto* core = (n64::Core*)user_data;
|
||||||
|
auto* breakpoint = (Breakpoint*)malloc(sizeof(Breakpoint));
|
||||||
|
breakpoint->addr = address;
|
||||||
|
breakpoint->next = NULL;
|
||||||
|
|
||||||
|
// Special case for this being the first breakpoint
|
||||||
|
if (core->debuggerState.breakpoints == NULL) {
|
||||||
|
core->debuggerState.breakpoints = breakpoint;
|
||||||
|
} else {
|
||||||
|
// Find end of the list
|
||||||
|
Breakpoint* tail = core->debuggerState.breakpoints;
|
||||||
|
while (tail->next != NULL) {
|
||||||
|
tail = tail->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
tail->next = breakpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugClearBreakpoint(void* user_data, u32 address) {
|
||||||
|
auto* core = (n64::Core*)user_data;
|
||||||
|
if (core->debuggerState.breakpoints == NULL) {
|
||||||
|
return; // No breakpoints set at all
|
||||||
|
} else if (core->debuggerState.breakpoints->addr == address) {
|
||||||
|
// Special case for the first breakpoint being the one we want to clear
|
||||||
|
Breakpoint* next = core->debuggerState.breakpoints->next;
|
||||||
|
free(core->debuggerState.breakpoints);
|
||||||
|
core->debuggerState.breakpoints = next;
|
||||||
|
} else {
|
||||||
|
// Find the breakpoint somewhere in the list and free it
|
||||||
|
Breakpoint* iter = core->debuggerState.breakpoints;
|
||||||
|
while (iter->next != NULL) {
|
||||||
|
if (iter->next->addr == address) {
|
||||||
|
Breakpoint* next = iter->next->next;
|
||||||
|
free(iter->next);
|
||||||
|
iter->next = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t DebugGetMemory(void* user_data, char* buffer, size_t length, u32 address, size_t bytes) {
|
||||||
|
auto* core = (n64::Core*)user_data;
|
||||||
|
printf("Checking memory at address 0x%08X\n", address);
|
||||||
|
int printed = 0;
|
||||||
|
for (int i = 0; i < bytes; i++) {
|
||||||
|
u8 value = core->mem.Read<u8>(core->cpu.regs, address + i, core->cpu.regs.pc);
|
||||||
|
printed += snprintf(buffer + (i*2), length, "%02X", value);
|
||||||
|
}
|
||||||
|
printf("Get memory: %ld bytes from 0x%08X: %d\n", bytes, address, printed);
|
||||||
|
return printed + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t DebugGetRegisterValue(void* user_data, char * buffer, size_t buffer_length, int reg) {
|
||||||
|
auto* core = (n64::Core*)user_data;
|
||||||
|
switch (reg) {
|
||||||
|
case 0 ... 31:
|
||||||
|
return snprintf(buffer, buffer_length, "%016llx", core->cpu.regs.gpr[reg]);
|
||||||
|
case 32:
|
||||||
|
return snprintf(buffer, buffer_length, "%08x", core->cpu.regs.cop0.status.raw);
|
||||||
|
case 33:
|
||||||
|
return snprintf(buffer, buffer_length, "%016llx", core->cpu.regs.lo);
|
||||||
|
case 34:
|
||||||
|
return snprintf(buffer, buffer_length, "%016llx", core->cpu.regs.hi);
|
||||||
|
case 35:
|
||||||
|
return snprintf(buffer, buffer_length, "%016llx", core->cpu.regs.cop0.badVaddr);
|
||||||
|
case 36:
|
||||||
|
return snprintf(buffer, buffer_length, "%08x", core->cpu.regs.cop0.cause.raw);
|
||||||
|
case 37:
|
||||||
|
printf("Sending PC: 0x%016llX\n", core->cpu.regs.pc);
|
||||||
|
return snprintf(buffer, buffer_length, "%016llx", core->cpu.regs.pc);
|
||||||
|
case 38 ... 71: // TODO FPU stuff
|
||||||
|
return snprintf(buffer, buffer_length, "%08x", 0);
|
||||||
|
default:
|
||||||
|
util::panic("debug get register {} value", reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t DebugGetGeneralRegisters(void* user_data, char * buffer, size_t buffer_length) {
|
||||||
|
auto* core = (n64::Core*)user_data;
|
||||||
|
printf("The buffer length is %zu!\n", buffer_length);
|
||||||
|
ssize_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!");
|
||||||
|
}
|
||||||
|
u64 reg = core->cpu.regs.gpr[i];
|
||||||
|
printed += snprintf(buffer + ofs, buffer_length - ofs, "%016llx", reg);
|
||||||
|
}
|
||||||
|
return printed;
|
||||||
|
}
|
||||||
149
src/frontend/imgui/debugger.hpp
Normal file
149
src/frontend/imgui/debugger.hpp
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#pragma once
|
||||||
|
#define GDBSTUB_IMPLEMENTATION
|
||||||
|
#include <gdbstub.h>
|
||||||
|
#include <util.hpp>
|
||||||
|
|
||||||
|
struct Core;
|
||||||
|
|
||||||
|
#define GDB_CPU_PORT 1337
|
||||||
|
|
||||||
|
struct Breakpoint {
|
||||||
|
u32 addr;
|
||||||
|
Breakpoint* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DebuggerState {
|
||||||
|
gdbstub_t* gdb;
|
||||||
|
bool broken;
|
||||||
|
int steps;
|
||||||
|
Breakpoint* breakpoints;
|
||||||
|
bool enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool CheckBreakpoint(DebuggerState& state, u32 addr) {
|
||||||
|
Breakpoint* cur = state.breakpoints;
|
||||||
|
while (cur != NULL) {
|
||||||
|
if (cur->addr == addr) {
|
||||||
|
util::print("Hit breakpoint at 0x{:08X}\n", addr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
void DebugStop(void* user_data);
|
||||||
|
void DebugStep(void* user_data);
|
||||||
|
void DebugSetBreakpoint(void* user_data, u32 address);
|
||||||
|
void DebugClearBreakpoint(void* user_data, u32 address);
|
||||||
|
ssize_t DebugGetMemory(void* user_data, char* buffer, size_t length, u32 address, size_t bytes);
|
||||||
|
ssize_t DebugGetRegisterValue(void* user_data, char * buffer, size_t buffer_length, int reg);
|
||||||
|
ssize_t DebugGetGeneralRegisters(void* user_data, char * buffer, size_t buffer_length);
|
||||||
49
src/frontend/imgui/widgets.cpp
Normal file
49
src/frontend/imgui/widgets.cpp
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#include <Window.hpp>
|
||||||
|
#include <nfd.h>
|
||||||
|
#include <debugger.hpp>
|
||||||
|
|
||||||
|
void Window::MainMenuBar(n64::Core& core) {
|
||||||
|
ImGui::PushFont(uiFont);
|
||||||
|
if(windowID == SDL_GetWindowID(SDL_GetMouseFocus())) {
|
||||||
|
ImGui::BeginMainMenuBar();
|
||||||
|
if (ImGui::BeginMenu("File")) {
|
||||||
|
if (ImGui::MenuItem("Open", "O")) {
|
||||||
|
nfdchar_t *outpath;
|
||||||
|
const nfdu8filteritem_t filter{"Nintendo 64 roms", "n64,z64,v64,N64,Z64,V64"};
|
||||||
|
nfdresult_t result = NFD_OpenDialog(&outpath, &filter, 1, nullptr);
|
||||||
|
if (result == NFD_OKAY) {
|
||||||
|
core.LoadROM(outpath);
|
||||||
|
NFD_FreePath(outpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Exit")) {
|
||||||
|
core.done = true;
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginMenu("Emulation")) {
|
||||||
|
if (ImGui::MenuItem("Reset")) {
|
||||||
|
core.Reset();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Stop")) {
|
||||||
|
core.Stop();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem(core.pause ? "Resume" : "Pause", nullptr, false, core.romLoaded)) {
|
||||||
|
core.TogglePause();
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
ImGui::EndMainMenuBar();
|
||||||
|
}
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::DebuggerWindow(n64::Core& core) {
|
||||||
|
ImGui::PushFont(uiFont);
|
||||||
|
ImGui::Begin("Debugger");
|
||||||
|
if(ImGui::Button("Step")) {
|
||||||
|
core.debuggerState.gdb->config.step(&core);
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@
|
|||||||
#include <Window.hpp>
|
#include <Window.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <util.hpp>
|
#include <util.hpp>
|
||||||
|
#include <debugger.hpp>
|
||||||
|
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
Core::Core() {
|
Core::Core() {
|
||||||
Stop();
|
Stop();
|
||||||
|
DebuggerInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Stop() {
|
void Core::Stop() {
|
||||||
@@ -15,6 +17,7 @@ void Core::Stop() {
|
|||||||
pause = true;
|
pause = true;
|
||||||
romLoaded = false;
|
romLoaded = false;
|
||||||
rom.clear();
|
rom.clear();
|
||||||
|
DebuggerCleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Reset() {
|
void Core::Reset() {
|
||||||
@@ -22,6 +25,8 @@ void Core::Reset() {
|
|||||||
mem.Reset();
|
mem.Reset();
|
||||||
pause = true;
|
pause = true;
|
||||||
romLoaded = false;
|
romLoaded = false;
|
||||||
|
DebuggerCleanup();
|
||||||
|
DebuggerInit();
|
||||||
if(!rom.empty()) {
|
if(!rom.empty()) {
|
||||||
LoadROM(rom);
|
LoadROM(rom);
|
||||||
}
|
}
|
||||||
@@ -34,6 +39,12 @@ void Core::LoadROM(const std::string& rom_) {
|
|||||||
romLoaded = true;
|
romLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::Step() {
|
||||||
|
MMIO& mmio = mem.mmio;
|
||||||
|
cpu.Step(mem);
|
||||||
|
mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp);
|
||||||
|
}
|
||||||
|
|
||||||
void Core::Run(Window& window) {
|
void Core::Run(Window& window) {
|
||||||
MMIO& mmio = mem.mmio;
|
MMIO& mmio = mem.mmio;
|
||||||
int cycles = 0;
|
int cycles = 0;
|
||||||
@@ -45,6 +56,16 @@ void Core::Run(Window& window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(;cycles <= mmio.vi.cyclesPerHalfline; cycles++) {
|
for(;cycles <= mmio.vi.cyclesPerHalfline; cycles++) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (debuggerState.enabled && CheckBreakpoint(debuggerState, cpu.regs.pc)) {
|
||||||
|
DebuggerBreakpointHit();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (debuggerState.broken) {
|
||||||
|
SDL_Delay(1000);
|
||||||
|
DebuggerTick();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
cpu.Step(mem);
|
cpu.Step(mem);
|
||||||
mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp);
|
mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp);
|
||||||
mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp);
|
mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp);
|
||||||
@@ -149,4 +170,49 @@ void Core::UpdateController(const u8* state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::DebuggerInit() {
|
||||||
|
gdbstub_config_t config;
|
||||||
|
memset(&config, 0, sizeof(gdbstub_config_t));
|
||||||
|
config.port = GDB_CPU_PORT;
|
||||||
|
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: %zu\n", config.target_config_length);
|
||||||
|
|
||||||
|
config.memory_map = memory_map;
|
||||||
|
config.memory_map_length = strlen(memory_map);
|
||||||
|
|
||||||
|
printf("Sizeof memory map: %zu\n", config.memory_map_length);
|
||||||
|
|
||||||
|
debuggerState.gdb = gdbstub_init(config);
|
||||||
|
if (!debuggerState.gdb) {
|
||||||
|
util::panic("Failed to initialize GDB stub!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::DebuggerTick() const {
|
||||||
|
gdbstub_tick(debuggerState.gdb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::DebuggerBreakpointHit() {
|
||||||
|
debuggerState.broken = true;
|
||||||
|
gdbstub_breakpoint_hit(debuggerState.gdb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::DebuggerCleanup() const {
|
||||||
|
if (debuggerState.enabled) {
|
||||||
|
gdbstub_term(debuggerState.gdb);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,27 +3,34 @@
|
|||||||
#include <Cpu.hpp>
|
#include <Cpu.hpp>
|
||||||
#include <Mem.hpp>
|
#include <Mem.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <debugger.hpp>
|
||||||
|
|
||||||
struct Window;
|
struct Window;
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
struct Core {
|
struct Core {
|
||||||
~Core() = default;
|
~Core() { Stop(); }
|
||||||
Core();
|
Core();
|
||||||
void Stop();
|
void Stop();
|
||||||
void Reset();
|
void Reset();
|
||||||
|
void Step();
|
||||||
void LoadROM(const std::string&);
|
void LoadROM(const std::string&);
|
||||||
void Run(Window&);
|
void Run(Window&);
|
||||||
void UpdateController(const u8*);
|
void UpdateController(const u8*);
|
||||||
void TogglePause() { pause = !pause; }
|
void TogglePause() { pause = !pause; }
|
||||||
VI& GetVI() { return mem.mmio.vi; }
|
VI& GetVI() { return mem.mmio.vi; }
|
||||||
|
|
||||||
|
void DebuggerInit();
|
||||||
|
void DebuggerTick() const;
|
||||||
|
void DebuggerBreakpointHit();
|
||||||
|
void DebuggerCleanup() const;
|
||||||
|
|
||||||
bool pause = true;
|
bool pause = true;
|
||||||
bool romLoaded = false;
|
bool romLoaded = false;
|
||||||
SDL_GameController* gamepad;
|
SDL_GameController* gamepad;
|
||||||
bool gamepadConnected = false;
|
bool gamepadConnected = false;
|
||||||
|
DebuggerState debuggerState;
|
||||||
bool done = false;
|
bool done = false;
|
||||||
std::string rom;
|
std::string rom;
|
||||||
private:
|
|
||||||
friend struct ::Window;
|
|
||||||
Mem mem;
|
Mem mem;
|
||||||
Cpu cpu;
|
Cpu cpu;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user