diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | bus.cpp | 22 | ||||
-rw-r--r-- | emulator.cpp | 168 | ||||
-rw-r--r-- | gdbremote_parser.cpp | 44 | ||||
-rw-r--r-- | gdbremote_parser.hpp | 23 | ||||
-rw-r--r-- | m68k_debugging.cpp | 144 | ||||
-rw-r--r-- | m68k_debugging.hpp | 60 | ||||
-rw-r--r-- | utils.hpp | 12 |
8 files changed, 458 insertions, 17 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index e5240aa..80b78d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ set(emulator_sources bus.cpp emulator.cpp gdbremote_parser.cpp + m68k_debugging.cpp ) set(musashi_m68k_sources musashi-m68k/m68kcpu.c @@ -58,4 +59,3 @@ add_executable(gdbremote include_directories( . ) - @@ -3,6 +3,7 @@ #include "bus.hpp" #include "musashi-m68k/m68k.h" +#include "utils.hpp" #include <cassert> #include <cstdarg> @@ -62,34 +63,35 @@ static inline unsigned int memory_read_concrete( (base[address + 2] << 8) | base[address + 3]; } - __builtin_unreachable(); + UNREACHABLE(); } static inline struct read_result memory_read( enum bitness bitness, unsigned int address) { - if (is_in_range(address, ROM_START, ROM_SIZE)) - return (struct read_result){ + if (is_in_range(address, ROM_START, ROM_SIZE)) { + return read_result{ memory_read_concrete(bitness, g_rom, address - ROM_START), true, }; - else if (is_in_range(address, RAM_START, RAM_SIZE)) - return (struct read_result){ + } else if (is_in_range(address, RAM_START, RAM_SIZE)) { + return read_result{ memory_read_concrete(bitness, g_ram, address - RAM_START), true, }; - else if (is_in_range(address, IO1_START, IO1_SIZE)) - return (struct read_result){ + } else if (is_in_range(address, IO1_START, IO1_SIZE)) { + return read_result{ memory_read_concrete(bitness, g_io1, address - IO1_START), true, }; - else if (is_in_range(address, IO2_START, IO2_SIZE)) - return (struct read_result){ + } else if (is_in_range(address, IO2_START, IO2_SIZE)) { + return read_result{ memory_read_concrete(bitness, g_io2, address - IO2_START), true, }; - return (struct read_result){0, false}; + } + return read_result{0, false}; } static inline void memory_write_concrete( diff --git a/emulator.cpp b/emulator.cpp index 0e3fb29..1605995 100644 --- a/emulator.cpp +++ b/emulator.cpp @@ -2,17 +2,124 @@ */ #include "bus.hpp" +#include "m68k_debugging.hpp" +#include "gdbremote_parser.hpp" #include "musashi-m68k/m68k.h" +#include <cassert> #include <cstdio> +#include <cstdint> #include <cstdlib> +#include <cstring> #include <cstdarg> #include <ctime> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <unistd.h> #if !defined(DEBUG_TRACE_INSTRUCTIONS) # define DEBUG_TRACE_INSTRUCTIONS 0 #endif +#define MESSAGE_BUFFER_SIZE 1024 + +char msg_buf[MESSAGE_BUFFER_SIZE]; + +static int set_socket_reuseaddr(int socket_fd) +{ + const int val = 1; + const socklen_t len = sizeof(val); + return setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, len); +} + +static inline struct sockaddr_in sockaddr_in_any_ip_with_port(uint16_t port) { + struct sockaddr_in server{}; + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(port); + return server; +} + +static int setup_socket(uint16_t port) +{ + // This creates the socket - or quits + const int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) { + perror("Could not create socket"); + return -1; + } + // Make TCP port reusable in case of kill or crash. + // Deliberate explanation: https://stackoverflow.com/a/3233022 + if (set_socket_reuseaddr(socket_fd) == -1) { + perror("setsockopt failed"); + close(socket_fd); + return -1; + } + puts("Socket created"); + const struct sockaddr_in server = sockaddr_in_any_ip_with_port(port); + if (bind(socket_fd, (struct sockaddr *)&server, sizeof(server)) == -1) { + perror("Bind failed"); + close(socket_fd); + return -1; + } + printf("Binding to 0.0.0.0:%u done\n", port); + return socket_fd; +} + +static inline void ConvertByteToHex(uint8_t byte, char* out) +{ + const uint8_t c1 = (byte >> 4) & 0x0f; + const uint8_t c2 = byte& 0x0f; + out[0] = static_cast<char>(c1 < 0xa ? c1 + '0' : c1 + ('a' - 0xa)); + out[1] = static_cast<char>(c2 < 0xa ? c2 + '0' : c2 + ('a' - 0xa)); +} + +static std::string CreateResponse( + M68KDebuggingControl& m68k_debug, + const GDBRemote::Packet& packet) +{ + using namespace GDBRemote; + if (0) { + } else if (packet.type == PacketType::kQueryHaltReason) { + return "S05"; + } else if (packet.type == PacketType::kStep) { + m68k_execute(1); + return "S05"; + } else if (packet.type == PacketType::kSetThreadForCont) { + return "OK"; + } else if (packet.type == PacketType::kQueryAttached) { + return "1"; + } else if (packet.type == PacketType::kInterrupt) { + return "OK"; + } else if (packet.type == PacketType::kReadMemory) { + const auto * const data = + reinterpret_cast<const PacketDataReadMemory*>(packet.data.get()); + const uint32_t offset = data->offset; + const uint32_t length = data->length; + auto ret_data = std::string(length * 2, '\0'); + for (uint32_t i = 0; i < length; i++) { + const uint8_t byte = m68k_debug.Read8(i + offset); + ConvertByteToHex(byte, &ret_data[i*2]); + } + return ret_data; + } else if (packet.type == PacketType::kReadGeneralRegisters) { + const M68KCPUState state = m68k_debug.GetCPUState(); + std::string result{}; + for (size_t i = 0; i < state.registers_count ;i++) + { + constexpr size_t value_size = 8; + char value[value_size + 1]{}; + const int ret = snprintf(value, value_size + 1, "%08x", state.registers[i]); + assert(ret == value_size); + result += std::string(value, value_size); + } + return result; + } else if (packet.type == PacketType::kContinueAskSupported) { + return "vCont:c:s"; + } + return ""; +} + static void exit_error(const char* fmt, ...) { va_list args; @@ -61,10 +168,65 @@ void m68k_instr_callback(int pc) m68k_disassemble(buff, pc, M68K_CPU_TYPE_68000); char buff2[100]; make_hex(buff2, pc, instr_size); - printf("E %08X: %-20s: %s\n", pc, buff2, buff); + printf(" %08X: %-20s: %s\n", pc, buff2, buff); fflush(stdout); } +int emulator(M68KDebuggingControl& m68k_debug) +{ + const int port = 3333; + const int socket_fd = setup_socket(port); + if (socket_fd == -1) + return EXIT_FAILURE; + printf("Listening TCP 0.0.0.0:%u\n", port); + listen(socket_fd, 4); // Mark socket as listener + struct sockaddr client_address; + socklen_t address_len; + const int conn_fd = accept(socket_fd, &client_address, &address_len); + if (conn_fd == -1) { + perror("Accept failed"); + close(socket_fd); + return EXIT_FAILURE; + } + puts("Connection accepted"); + GDBRemote::ExchangeContext exchange_ctx{}; + ssize_t read_size; + while ((read_size = recv(conn_fd, msg_buf, MESSAGE_BUFFER_SIZE, 0)) > 0) { + for (size_t i = 0; i < static_cast<size_t>(read_size); i++) { + const auto res = exchange_ctx.Consume(msg_buf[i]); + if (res == nullptr) continue; + if (res->packet.length() > 0) { + if (0) printf("<- \"%s\"\n", exchange_ctx.GetLastPacket().c_str()); + const auto packet = GDBRemote::Packet::Parse(res->packet); + if (0) printf( + " Packet type: \"%s\"\n", + GDBRemote::Packet::PacketTypeToString(packet.type)); + } + if (res->ack.length() > 0) { + if (0) printf("-> \"%s\"\n", res->ack.c_str()); + if (send(conn_fd, &res->ack[0], res->ack.length(), 0) == -1) + perror("Send failed (ack/nak)"); + } + if (res->packet.length() > 0) { + const auto packet = GDBRemote::Packet::Parse(res->packet); + const auto response = + exchange_ctx.WrapDataToSend(CreateResponse(m68k_debug, packet)); + if (0) printf("-> \"%s\"\n", response.c_str()); + if (send(conn_fd, &response[0], response.length(), 0) == -1) + perror("Send failed (response)"); + } + } + memset(msg_buf, '\0', MESSAGE_BUFFER_SIZE); + } + if (read_size == 0) { + puts("Client disconnected"); + } else if (read_size == -1) { + perror("Recv failed"); + } + close(socket_fd); + return 0; +} + int main(int argc, char* argv[]) { if (argc != 2) @@ -87,7 +249,9 @@ int main(int argc, char* argv[]) m68k_set_cpu_type(M68K_CPU_TYPE_68000); m68k_pulse_reset(); - while (1) + M68KDebuggingControl m68k_debug{}; + emulator(m68k_debug); + while (0) { // Values to execute determine the interleave rate. // Smaller values allow for more accurate interleaving with multiple diff --git a/gdbremote_parser.cpp b/gdbremote_parser.cpp index d8f4f50..204690d 100644 --- a/gdbremote_parser.cpp +++ b/gdbremote_parser.cpp @@ -24,6 +24,13 @@ static inline bool isseparator(uint8_t byte) return ':' == byte || ';' == byte || ',' == byte; } +static inline bool IsTokenTypeSeparator(TokenType token_type) +{ + return token_type == TokenType::kSeparatorColon || + token_type == TokenType::kSeparatorComma || + token_type == TokenType::kSeparatorSemicolon; +} + static inline TokenType separatorTypeFromByte(uint8_t byte) { if (':' == byte) return TokenType::kSeparatorColon; @@ -143,14 +150,47 @@ struct Command { { return Packet{PacketType::kInterrupt}; } - static Packet parseContinue(const std::vector<Token>&&) + static Packet parseContinue(const std::vector<Token>&& tokens) { + const std::string first = tokens[0].data; + constexpr size_t command_len = strlen("vCont"); + // Check if it is "vCont?" command + if (first.length() > command_len && first[command_len] == '?') { + return Packet{PacketType::kContinueAskSupported}; + } + // TODO arguments return Packet{PacketType::kContinue}; } static Packet parseReadGeneralRegisters(const std::vector<Token>&&) { return Packet{PacketType::kReadGeneralRegisters}; } + static Packet parseReadMemory(const std::vector<Token>&& tokens) + { + if (tokens.size() < 3 || + tokens[0].type != TokenType::kArbitrary || + !IsTokenTypeSeparator(tokens[1].type) || + tokens[2].type != TokenType::kNumeric) + { + return Packet::None(); + } + constexpr size_t command_len = strlen("m"); + const std::string first = tokens[0].data; + const std::string second = tokens[2].data; + const uint32_t offset = strtol( + first.substr(command_len, first.length()-command_len).c_str(), + nullptr, + 16); + const uint32_t lenght = strtol(second.c_str(), nullptr, 16); + return Packet{ + PacketType::kReadMemory, + std::make_unique<const PacketDataReadMemory>(offset, lenght), + }; + } + static Packet parseStep(const std::vector<Token>&&) + { + return Packet{PacketType::kStep}; + } }; static const Command commands[] = { @@ -169,7 +209,9 @@ static const Command commands[] = { { "\x03", Command::parseInterrupt }, { "vCtrlC", Command::parseInterrupt }, { "vCont", Command::parseContinue }, + { "m", Command::parseReadMemory }, { "g", Command::parseReadGeneralRegisters }, + { "s", Command::parseStep }, }; Packet Packet::Parse(std::string packet_data) diff --git a/gdbremote_parser.hpp b/gdbremote_parser.hpp index 2e9fd88..88a0cf7 100644 --- a/gdbremote_parser.hpp +++ b/gdbremote_parser.hpp @@ -57,14 +57,18 @@ enum class PacketType: int { kEnableExtendedMode, kInterrupt, kContinue, + kContinueAskSupported, kReadGeneralRegisters, + kReadMemory, + kStep, }; struct PacketData { - PacketData() = delete; + virtual ~PacketData() {}; +protected: PacketData(const PacketData&) = delete; PacketData(PacketData&&) = delete; - virtual ~PacketData() {} + PacketData() = default; }; struct PacketDataSupportedFeatures: public PacketData { @@ -72,9 +76,16 @@ struct PacketDataSupportedFeatures: public PacketData { virtual ~PacketDataSupportedFeatures() {} }; +struct PacketDataReadMemory: public PacketData { + PacketDataReadMemory(uint32_t a_offset, uint32_t a_length) + : offset(a_offset), length(a_length) {} + uint32_t offset{}, length{}; + virtual ~PacketDataReadMemory() {} +}; + struct Packet { const PacketType type{}; - const std::unique_ptr<PacketData> data{nullptr}; + const std::unique_ptr<const PacketData> data{nullptr}; /** Convert raw packet data into a Packet * @@ -118,8 +129,14 @@ struct Packet { return "vCtrlC"; case PacketType::kContinue: return "vCont"; + case PacketType::kContinueAskSupported: + return "vCont?"; case PacketType::kReadGeneralRegisters: return "g"; + case PacketType::kReadMemory: + return "m"; + case PacketType::kStep: + return "s"; } return "<unknown>"; } diff --git a/m68k_debugging.cpp b/m68k_debugging.cpp new file mode 100644 index 0000000..f9b0b38 --- /dev/null +++ b/m68k_debugging.cpp @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#include "m68k_debugging.hpp" +#include "musashi-m68k/m68k.h" +#include "utils.hpp" + +#include <cstdlib> +#include <cassert> + +static inline m68k_register_t ConvertRegisterId(M68KRegister register_id) +{ + switch (register_id) { + case M68KRegister::kD0: + return M68K_REG_D0; + case M68KRegister::kD1: + return M68K_REG_D1; + case M68KRegister::kD2: + return M68K_REG_D2; + case M68KRegister::kD3: + return M68K_REG_D3; + case M68KRegister::kD4: + return M68K_REG_D4; + case M68KRegister::kD5: + return M68K_REG_D5; + case M68KRegister::kD6: + return M68K_REG_D6; + case M68KRegister::kD7: + return M68K_REG_D7; + case M68KRegister::kA0: + return M68K_REG_A0; + case M68KRegister::kA1: + return M68K_REG_A1; + case M68KRegister::kA2: + return M68K_REG_A2; + case M68KRegister::kA3: + return M68K_REG_A3; + case M68KRegister::kA4: + return M68K_REG_A4; + case M68KRegister::kA5: + return M68K_REG_A5; + case M68KRegister::kA6: + return M68K_REG_A6; + case M68KRegister::kA7: + return M68K_REG_A7; + case M68KRegister::kPS: + return M68K_REG_SR; + case M68KRegister::kPC: + return M68K_REG_PC; + case M68KRegister::kFP0: + case M68KRegister::kFPC: + case M68KRegister::kFPS: + case M68KRegister::kFPI: + case M68KRegister::kRegistersCount: + assert(!"Unsupported register_id"); + break; + } + assert(!"Unknown register_id"); + UNREACHABLE(); +} + +uint32_t M68KDebuggingControl::GetRegister(M68KRegister register_id) const +{ + return m68k_get_reg(nullptr, ConvertRegisterId(register_id)); +} + +M68KCPUState M68KDebuggingControl::GetCPUState() const +{ + return M68KCPUState{ + static_cast<size_t>(M68KRegister::kPC) + 1, + { + GetRegister(M68KRegister::kD0), + GetRegister(M68KRegister::kD1), + GetRegister(M68KRegister::kD2), + GetRegister(M68KRegister::kD3), + GetRegister(M68KRegister::kD4), + GetRegister(M68KRegister::kD5), + GetRegister(M68KRegister::kD6), + GetRegister(M68KRegister::kD7), + GetRegister(M68KRegister::kA0), + GetRegister(M68KRegister::kA1), + GetRegister(M68KRegister::kA2), + GetRegister(M68KRegister::kA3), + GetRegister(M68KRegister::kA4), + GetRegister(M68KRegister::kA5), + GetRegister(M68KRegister::kA6), + GetRegister(M68KRegister::kA7), + GetRegister(M68KRegister::kPS), + GetRegister(M68KRegister::kPC), + }, + }; +} + +void M68KDebuggingControl::SetRegister(M68KRegister register_id, uint32_t value) +{ + return m68k_set_reg(ConvertRegisterId(register_id), value); +} + +void M68KDebuggingControl::Reset() +{ + m68k_pulse_reset(); +} + +void M68KDebuggingControl::Continue() +{ + // TODO + abort(); +} + +void M68KDebuggingControl::Pause() +{ + // TODO + abort(); +} + +uint8_t M68KDebuggingControl::Read8(uint32_t address) const +{ + return static_cast<uint8_t>(m68k_read_memory_8(address & 0x00FFFFFF)); +} + +uint16_t M68KDebuggingControl::Read16(uint32_t address) const +{ + return static_cast<uint16_t>(m68k_read_memory_16(address & 0x00FFFFFF)); +} + +uint32_t M68KDebuggingControl::Read32(uint32_t address) const +{ + return static_cast<uint32_t>(m68k_read_memory_32(address & 0x00FFFFFF)); +} + +void M68KDebuggingControl::Write8(uint32_t address, uint8_t value) +{ + m68k_write_memory_8(address, value); +} + +void M68KDebuggingControl::Write16(uint32_t address, uint16_t value) +{ + m68k_write_memory_16(address, value); +} + +void M68KDebuggingControl::Write32(uint32_t address, uint32_t value) +{ + m68k_write_memory_32(address, value); +} diff --git a/m68k_debugging.hpp b/m68k_debugging.hpp new file mode 100644 index 0000000..4892493 --- /dev/null +++ b/m68k_debugging.hpp @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#pragma once + +#include <cstdint> +#include <cstddef> + +enum class M68KRegister: size_t { + kD0 = 0, + kD1 = 1, + kD2 = 2, + kD3 = 3, + kD4 = 4, + kD5 = 5, + kD6 = 6, + kD7 = 7, + kA0 = 8, + kA1 = 9, + kA2 = 10, + kA3 = 11, + kA4 = 12, + kA5 = 13, + kA6 = 14, /* Address of executing stack frame */ + kFP = kA6, + kA7 = 15, /* Address of top of stack (Stack pointer) */ + kSP = kA7, + kPS = 16, /* Processor status (Condition code register) */ + kPC = 17, /* Program counter */ + kFP0 = 18, /* Floating point register 0 */ + kFPC = 26, /* 68881 control register */ + kFPS = 27, /* 68881 status register */ + kFPI = 28, /* Floating point register 1 */ + kRegistersCount, +}; + +struct M68KCPUState { + size_t registers_count{}; + uint32_t registers[static_cast<size_t>(M68KRegister::kRegistersCount)]{}; +}; + +class M68KDebuggingControl { +public: + M68KDebuggingControl() = default; + uint32_t GetRegister(M68KRegister) const; + M68KCPUState GetCPUState() const; + void SetRegister(M68KRegister, uint32_t value); + void Reset(); + void Continue(); + void Pause(); + uint8_t Read8(uint32_t address) const; + uint16_t Read16(uint32_t address) const; + uint32_t Read32(uint32_t address) const; + void Write8(uint32_t address, uint8_t value); + void Write16(uint32_t address, uint16_t value); + void Write32(uint32_t address, uint32_t value); +private: + M68KDebuggingControl(M68KDebuggingControl&&) = delete; + M68KDebuggingControl(const M68KDebuggingControl&) = delete; +}; diff --git a/utils.hpp b/utils.hpp new file mode 100644 index 0000000..4a5e84d --- /dev/null +++ b/utils.hpp @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#pragma once + +#ifdef __clang__ +# define UNREACHABLE __builtin_unreachable +#elif __GNUC__ +# define UNREACHABLE __builtin_unreachable +#else +# define UNREACHABLE +#endif |