/* SPDX-License-Identifier: Unlicense */ #include "bus.hpp" #include "graphics.hpp" #include "vdp.hpp" #include "m68k_debugging.hpp" #include "gdbremote_parser.hpp" #include "utils.hpp" #include "musashi-m68k/m68k.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(DEBUG_TRACE_INSTRUCTIONS) # define DEBUG_TRACE_INSTRUCTIONS 0 #endif #if !defined(DEBUG_TRACE_GDB_REMOTE) # define DEBUG_TRACE_GDB_REMOTE 0 #endif #define MESSAGE_BUFFER_SIZE 1024 unsigned char g_rom[ROM_SIZE] = {}; unsigned char g_ram[RAM_SIZE] = {}; unsigned char g_sound_ram[SOUND_RAM_SIZE] = {}; unsigned char g_io1[IO1_SIZE] = {}; unsigned char g_psg[PSG_SIZE] = {}; VDP g_vdp(VDP_START); std::vector code_bkpts{}, read_bkpts{}, write_bkpts{}, access_bkpts{}; uint64_t g_cycles_counter = 0; bool quit{}; template struct Backtrace { static_assert(S > 0, "Backtrace size cannot be zero"); void Push(T value) { head = (head + 1) % S; buffer[head] = value; } void Normalize(void) { constexpr auto last_offset = S - 1; for (; head < last_offset; head++) { const T tmp = buffer[last_offset]; memmove(&buffer[1], &buffer[0], (S - 1) * sizeof(buffer[0])); buffer[0] = tmp; } } static constexpr size_t Size(void) { return S; } T buffer[S]{}; size_t head{}; }; static constexpr struct timespec kOneMillisecond{0, 1000000}; static char msg_buf[MESSAGE_BUFFER_SIZE]; static M68KDebuggingControl g_m68k_debug{}; static bool g_no_ack_mode{}; static Backtrace g_pc_backtrace{}; static int set_socket_reuseaddr(const 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(const 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 inline int SetNonblock(const int fd) { const int flags = fcntl(fd, F_GETFL); if (flags == -1) { return errno; } const int ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK); if (ret == -1) { return errno; } return 0; } static int setup_socket(const 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; } // Set O_NONBLOCK for socket const int ret = SetNonblock(socket_fd); if (ret) { fprintf(stderr, "fcntl(socket_fd, F_SETFL O_NONBLOCK): %s\n", strerror(ret)); close(socket_fd); 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 bool IsReset(const std::string& command) { if (command == "reset") return true; if (command == "reset halt") return true; if (command == "system_reset") return true; return false; } static std::string CreateResponse( M68KDebuggingControl& m68k_debug, const GDBRemote::Packet& packet) { using namespace GDBRemote; switch (packet.type) { case PacketType::kNone: break; case PacketType::kQuerySupported: return "PacketSize=400000" // PacketSize number is in HEX // These commented-out options are from OpenOCD // TODO ";qXfer:memory-map:read+" // TODO ";qXfer:features:read+" // TODO ";qXfer:threads:read+" ";QStartNoAckMode+" // It makes GDB shit fast! ";vContSupported+" // We support "vCont?" packet ; case PacketType::kQueryHaltReason: return "S05"; // TODO real reason case PacketType::kQueryC: break; case PacketType::kQueryFThreadInfo: break; case PacketType::kQuerySThreadInfo: break; case PacketType::kQueryRTOSThreadInfo: break; case PacketType::kQueryAttached: return "1"; case PacketType::kQueryTraceStatus: break; case PacketType::kQueryRcmd: { const auto * const packet_data = static_cast(packet.data.get()); if (IsReset(packet_data->data)) { m68k_pulse_reset(); // I don't want to skip reset cycles here, because GDB does not // re-read registers every time, and if we skip reset cycles // here, the consequent "stepi" in gdb will end up on second // instruction of reset handler. If we don't skip reset cycles, // the first "stepi" after "monitor reset" will land us on the // first instruction in reset handler - I'd prefer this exact // behavior for now. return "OK"; } } break; case PacketType::kQStartNoAckMode: g_no_ack_mode = true; return "OK"; case PacketType::kSetThreadForCont: return "OK"; case PacketType::kSetThreadForOps: break; case PacketType::kVMustReplyEmpty: break; case PacketType::kEnableExtendedMode: break; case PacketType::kInterrupt: g_m68k_debug.RaiseBreakpoint(); return "S05"; // TODO real reason case PacketType::kContinue: m68k_debug.SetRunning(true); return "OK"; case PacketType::kContinueAskSupported: return "vCont:c:s"; case 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 = std::string_view("12345678").length(); char value[value_size + 1]{}; const int ret = snprintf(value, value_size + 1, "%08x", state.registers[i]); (void) ret; assert(ret == value_size); result += std::string(value, value_size); } return result; } case PacketType::kWriteGeneralRegisters: { const auto * const data = static_cast(packet.data.get()); const auto& registers = data->registers; for (size_t i = 0; i < registers.size(); i++) { m68k_debug.SetRegister(static_cast(i), registers[i]); } return "OK"; } break; case PacketType::kReadRegister: { const auto * const data = static_cast(packet.data.get()); const auto reg_id = static_cast(data->reg_id); if (reg_id >= M68KRegister::kRegistersCount) { return "E00"; } const uint32_t value = m68k_debug.GetRegister(reg_id); constexpr size_t value_size = std::string_view("12345678").length(); char value_str[value_size + 1]{}; const int ret = snprintf(value_str, value_size + 1, "%08x", value); (void) ret; assert(ret == value_size); return std::string(value_str, value_size); } break; case PacketType::kWriteRegister: { const auto * const data = static_cast(packet.data.get()); const auto reg_id = static_cast(data->reg_id); const auto value = data->value; if (reg_id >= M68KRegister::kRegistersCount) { return "E00"; } m68k_debug.SetRegister(reg_id, value); return "OK"; } break; case PacketType::kReadMemory: { const auto * const data = static_cast(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); Utils::ConvertByteToHex(byte, &ret_data[i*2]); } return ret_data; } case PacketType::kWriteMemory: { const auto * const packet_data = static_cast(packet.data.get()); const uint32_t offset = packet_data->offset; const uint32_t length = packet_data->length; const auto write_data = packet_data->data; for (uint32_t i = 0; i < length; i++) { m68k_debug.Write8(i + offset, write_data[i]); } return "OK"; } case PacketType::kStep: g_cycles_counter += m68k_execute(1); return "S05"; // TODO real reason case PacketType::kSetBreakpoint: { const auto * const bkpt_data = static_cast(packet.data.get()); m68k_debug.SetBreakpoint(bkpt_data->type, bkpt_data->offset, bkpt_data->length); return "OK"; } case PacketType::kDeleteBreakpoint: { const auto * const bkpt_data = static_cast(packet.data.get()); m68k_debug.RemoveBreakpoint(bkpt_data->type, bkpt_data->offset, bkpt_data->length); return "OK"; } } return ""; } static void exit_error(const char* fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); exit(EXIT_FAILURE); } /* Called when the CPU pulses the RESET line */ void m68k_reset_callback(void) { g_vdp.Reset(); } /* Called when the CPU acknowledges an interrupt */ int m68k_irq_ack(int level) { (void) level; // TODO printf("IRQ level %d acknowledged\n", level); return level; } static void make_hex(char* buff, unsigned int pc, unsigned int length) { char* ptr = buff; for (;length>0;length -= 2) { sprintf(ptr, "%04x", m68k_read_disassembler_16(pc)); pc += 2; ptr += 4; if (length > 2) *ptr++ = ' '; } } static void PrintInstructionTrace(const uint32_t pc) { char buff[100]; const unsigned int instr_size = m68k_disassemble(buff, pc, M68K_CPU_TYPE_68000); char buff2[100]; make_hex(buff2, pc, instr_size); printf(" %08x: %-20s: %s\n", pc, buff2, buff); fflush(stdout); } // TODO m68k_set_illg_instr_callback for true software breakpoint "4e4f" int m68k_instr_callback(const int pc) { g_pc_backtrace.Push(pc); const auto it = std::find_if( code_bkpts.begin(), code_bkpts.end(), [&](const Breakpoint& b) { return b.offset == static_cast(pc); }); if (it != code_bkpts.end()) { g_m68k_debug.RaiseBreakpoint(); m68k_end_timeslice(); printf("Breakpoint @ 0x%08x\n", pc); g_pc_backtrace.Normalize(); printf("PC backtrace (size=%zu):\n", g_pc_backtrace.Size()); for (size_t i = 0; i < g_pc_backtrace.Size(); i++) { PrintInstructionTrace(g_pc_backtrace.buffer[i]); } return 1; } if (DEBUG_TRACE_INSTRUCTIONS) { PrintInstructionTrace(pc); } return 0; } void m68k_breakpoint_callback(void) { g_pc_backtrace.Normalize(); printf("PC backtrace (size=%zu):\n", g_pc_backtrace.Size()); for (size_t i = 0; i < g_pc_backtrace.Size(); i++) { PrintInstructionTrace(g_pc_backtrace.buffer[i]); } g_m68k_debug.RaiseBreakpoint(); m68k_end_timeslice(); } void ParseAndReact( const int conn_fd, GDBRemote::ExchangeContext& exchange_ctx, M68KDebuggingControl& m68k_debug, const char* const msg_data, const size_t msg_data_len) { for (size_t i = 0; i < static_cast(msg_data_len); i++) { const auto res = exchange_ctx.Consume(msg_data[i]); if (res == nullptr) continue; if (DEBUG_TRACE_GDB_REMOTE && res->packet.length() > 0) { printf("<- \"%s\"\n", exchange_ctx.GetLastPacket().c_str()); const auto packet = GDBRemote::Packet::Parse(res->packet); printf( " Packet type: \"%s\"\n", GDBRemote::Packet::PacketTypeToString(packet.type)); } if (res->ack.length() > 0 && !g_no_ack_mode) { if (DEBUG_TRACE_GDB_REMOTE) 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 (DEBUG_TRACE_GDB_REMOTE) printf("-> \"%s\"\n", response.c_str()); if (send(conn_fd, &response[0], response.length(), 0) == -1) perror("Send failed (response)"); } } } static void RunSingleVideoCycle(M68KDebuggingControl& m68k_debug, Graphics& graphics) { do { g_cycles_counter += m68k_execute(1000); if (m68k_debug.HasBreakpoint()) { return; } } while (!g_vdp.Scanline()); graphics.Render(g_vdp); g_cycles_counter += m68k_execute(8000); } int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics) { const int port = 3333; const int socket_fd = setup_socket(port); if (socket_fd == -1) return EXIT_FAILURE; // Mark socket as listener if (listen(socket_fd, 4) == -1 ) { perror("Listen failed"); close(socket_fd); return EXIT_FAILURE; } printf("Listening TCP 0.0.0.0:%u\n", port); while (!quit) { struct sockaddr client_address{}; socklen_t address_len{}; const int conn_fd = accept(socket_fd, &client_address, &address_len); if (conn_fd == -1 ) { if (errno == EWOULDBLOCK) { // TODO turn off O_NONBLOCK and poll instead of sleep, only use // nonlock when freerunning clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr); continue; } perror("Accept failed"); close(socket_fd); return EXIT_FAILURE; } puts("Connection accepted"); // Set O_NONBLOCK for connection const int ret = SetNonblock(conn_fd); if (ret == -1) { perror("fcntl(conn_fd, F_SETFL O_NONBLOCK)"); return -1; } GDBRemote::ExchangeContext exchange_ctx{}; while (!quit) { const ssize_t read_size = recv(conn_fd, msg_buf, MESSAGE_BUFFER_SIZE, 0); if (read_size > 0) { ParseAndReact(conn_fd, exchange_ctx, m68k_debug, msg_buf, read_size); continue; } else if (read_size == 0) { puts("Client disconnected"); break; } else if (read_size == -1 && errno != EWOULDBLOCK) { perror("Recv failed"); break; } if (m68k_debug.IsRunning()) { RunSingleVideoCycle(m68k_debug, graphics); if (m68k_debug.HasBreakpoint()) { m68k_debug.SetRunning(false); printf("ResetPendingBreakpoint"); m68k_debug.ResetPendingBreakpoint(); const auto response = exchange_ctx.WrapDataToSend("S05"); if (DEBUG_TRACE_GDB_REMOTE) printf("-> \"%s\"\n", response.c_str()); if (send(conn_fd, &response[0], response.length(), 0) == -1) perror("Send failed (response)"); } } // TODO turn off O_NONBLOCK and poll instead of sleep, only use // nonlock when freerunning clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr); } close(conn_fd); g_no_ack_mode = false; // TODO move to GDB::ExchangeContext } close(socket_fd); return 0; } void sigint_handler(int sig) { printf("Signal %d received, exiting\n",sig); quit = true; } int main(int argc, char* argv[]) { if (argc != 2) { printf("Usage: %s \n", argv[0]); exit(-1); } FILE* const fhandle = fopen(argv[1], "rb"); if (fhandle == NULL) exit_error("Unable to open %s", argv[1]); const size_t fread_ret = fread(g_rom, 1, ROM_SIZE, fhandle); if (fread_ret <= 0) exit_error("Error reading %s", argv[1]); printf("Read into ROM %zu bytes\n", fread_ret); struct sigaction sa; sa.sa_handler = sigint_handler; sigaction(SIGINT, &sa, NULL); Graphics graphics{}; if (!graphics.IsOk()) { return EXIT_FAILURE; } m68k_init(); m68k_set_cpu_type(M68K_CPU_TYPE_68000); m68k_pulse_reset(); g_cycles_counter += m68k_execute(1); // Skip reset cycles emulator(g_m68k_debug, graphics); return EXIT_SUCCESS; }