diff --git a/external/gdbstub.h b/external/gdbstub.h new file mode 100644 index 00000000..98ffdc2d --- /dev/null +++ b/external/gdbstub.h @@ -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 +#include + +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 +#include +#include +#include +#include + +#if defined(WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#pragma comment (lib, "Ws2_32.lib") +#define close closesocket +#else +#include +#include +#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 \ No newline at end of file diff --git a/external/parallel-rdp/CMakeLists.txt b/external/parallel-rdp/CMakeLists.txt index 8592ae27..90ef404b 100644 --- a/external/parallel-rdp/CMakeLists.txt +++ b/external/parallel-rdp/CMakeLists.txt @@ -64,6 +64,7 @@ target_include_directories(parallel-rdp PUBLIC parallel-rdp-standalone .. ../../src/frontend + ../../src/frontend/imgui ../../src ../imgui ../imgui/imgui diff --git a/external/parallel-rdp/ParallelRDPWrapper.cpp b/external/parallel-rdp/ParallelRDPWrapper.cpp index d420b129..1462eaf3 100644 --- a/external/parallel-rdp/ParallelRDPWrapper.cpp +++ b/external/parallel-rdp/ParallelRDPWrapper.cpp @@ -253,8 +253,9 @@ void UpdateScreen(n64::Core& core, Window& imguiWindow, Util::IntrusivePtr cmd = wsi->get_device().request_command_buffer(); 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(); wsi->get_device().submit(cmd); diff --git a/resources/OpenSans.ttf b/resources/OpenSans.ttf new file mode 100644 index 00000000..9cae0f79 Binary files /dev/null and b/resources/OpenSans.ttf differ diff --git a/resources/Sweet16.ttf b/resources/Sweet16.ttf new file mode 100644 index 00000000..ba6eeea6 Binary files /dev/null and b/resources/Sweet16.ttf differ diff --git a/src/frontend/imgui/CMakeLists.txt b/src/frontend/imgui/CMakeLists.txt index 58f6ea63..8347e628 100644 --- a/src/frontend/imgui/CMakeLists.txt +++ b/src/frontend/imgui/CMakeLists.txt @@ -9,7 +9,9 @@ find_package(fmt REQUIRED) add_library(frontend-imgui STATIC Window.cpp - Window.hpp) + Window.hpp + widgets.cpp + debugger.hpp debugger.cpp) 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/util ../../../external/parallel-rdp/parallel-rdp-standalone/volk) -target_link_libraries(frontend-imgui PUBLIC SDL2main SDL2 imgui nfd fmt) \ No newline at end of file +target_link_libraries(frontend-imgui PUBLIC ws2_32 SDL2main SDL2 imgui nfd fmt) \ No newline at end of file diff --git a/src/frontend/imgui/Window.cpp b/src/frontend/imgui/Window.cpp index b379ca5a..67dce8bd 100644 --- a/src/frontend/imgui/Window.cpp +++ b/src/frontend/imgui/Window.cpp @@ -48,12 +48,9 @@ void Window::InitImgui() { ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // Setup Dear ImGui style ImGui::StyleColorsDark(); - //ImGui::StyleColorsLight(); instance = GetVkInstance(); physicalDevice = GetVkPhysicalDevice(); @@ -110,50 +107,19 @@ void Window::InitImgui() { initInfo.CheckVkResultFn = check_vk_result; ImGui_ImplVulkan_Init(&initInfo, GetVkRenderPass()); - // Load Fonts - // - 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. - // - 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(); + uiFont = io.Fonts->AddFontFromFileTTF("resources/OpenSans.ttf", 15.f); + codeFont = io.Fonts->AddFontFromFileTTF("resources/Sweet16.ttf", 15.f); - // Upload Fonts { VkCommandBuffer commandBuffer = GetVkCommandBuffer(); ImGui_ImplVulkan_CreateFontsTexture(commandBuffer); 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() { VkResult err = vkDeviceWaitIdle(device); check_vk_result(err); - vkDestroySampler(device, screenSampler, nullptr); ImGui_ImplVulkan_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); @@ -162,73 +128,17 @@ Window::~Window() { SDL_Quit(); } -ImDrawData* Window::Present(const Util::IntrusivePtr& image, n64::Core& core) { +ImDrawData* Window::Present(n64::Core& core) { ImGui_ImplVulkan_NewFrame(); ImGui_ImplSDL2_NewFrame(window); ImGui::NewFrame(); - Render(image, core); + Render(core); ImGui::Render(); return ImGui::GetDrawData(); } -void Window::Render(const Util::IntrusivePtr& image, n64::Core& core) { - 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::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(); +void Window::Render(n64::Core& core) { + MainMenuBar(core); } diff --git a/src/frontend/imgui/Window.hpp b/src/frontend/imgui/Window.hpp index ff40520f..80660f58 100644 --- a/src/frontend/imgui/Window.hpp +++ b/src/frontend/imgui/Window.hpp @@ -10,19 +10,20 @@ struct Window { explicit Window(n64::Core& core); ~Window(); - ImDrawData* Present(const Util::IntrusivePtr&, n64::Core& core); + ImDrawData* Present(n64::Core& core); [[nodiscard]] bool gotClosed(SDL_Event event); + ImFont *uiFont, *codeFont; + u32 windowID; private: SDL_Window* window; - u32 windowID; void InitSDL(); void InitImgui(); - void Render(const Util::IntrusivePtr&, n64::Core& core); + void Render(n64::Core& core); + void MainMenuBar(n64::Core& core); + void DebuggerWindow(n64::Core& core); VkPhysicalDevice physicalDevice{}; - VkSampler screenSampler{}; - VkDescriptorSet screenTexture{}; VkDevice device{}; uint32_t queueFamily{uint32_t(-1)}; VkQueue queue{}; diff --git a/src/frontend/imgui/debugger.cpp b/src/frontend/imgui/debugger.cpp new file mode 100644 index 00000000..768fa114 --- /dev/null +++ b/src/frontend/imgui/debugger.cpp @@ -0,0 +1,115 @@ +#include +#include + +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(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; +} \ No newline at end of file diff --git a/src/frontend/imgui/debugger.hpp b/src/frontend/imgui/debugger.hpp new file mode 100644 index 00000000..10017275 --- /dev/null +++ b/src/frontend/imgui/debugger.hpp @@ -0,0 +1,149 @@ +#pragma once +#define GDBSTUB_IMPLEMENTATION +#include +#include + +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 = + "" + "" + "" + "mips:4000" + "none" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + "" + " " + " " + " " + " " + "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + +const char* memory_map = + "" + "" + "" + "" + + "" // TODO finish + "" // RDRAM + "" // RSP DMEM + "" // RSP IMEM + "" // PIF ROM + + "" // TODO finish + "" // RDRAM + "" // RSP DMEM + "" // RSP IMEM + "" // PIF ROM + ""; + + +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); \ No newline at end of file diff --git a/src/frontend/imgui/widgets.cpp b/src/frontend/imgui/widgets.cpp new file mode 100644 index 00000000..05493915 --- /dev/null +++ b/src/frontend/imgui/widgets.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +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(); +} \ No newline at end of file diff --git a/src/n64/Core.cpp b/src/n64/Core.cpp index 030f13d5..dcff8e85 100644 --- a/src/n64/Core.cpp +++ b/src/n64/Core.cpp @@ -3,10 +3,12 @@ #include #include #include +#include namespace n64 { Core::Core() { Stop(); + DebuggerInit(); } void Core::Stop() { @@ -15,6 +17,7 @@ void Core::Stop() { pause = true; romLoaded = false; rom.clear(); + DebuggerCleanup(); } void Core::Reset() { @@ -22,6 +25,8 @@ void Core::Reset() { mem.Reset(); pause = true; romLoaded = false; + DebuggerCleanup(); + DebuggerInit(); if(!rom.empty()) { LoadROM(rom); } @@ -34,6 +39,12 @@ void Core::LoadROM(const std::string& rom_) { 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) { MMIO& mmio = mem.mmio; int cycles = 0; @@ -45,6 +56,16 @@ void Core::Run(Window& window) { } 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); 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); + } +} } diff --git a/src/n64/Core.hpp b/src/n64/Core.hpp index 47d4ed79..6a5fe294 100644 --- a/src/n64/Core.hpp +++ b/src/n64/Core.hpp @@ -3,27 +3,34 @@ #include #include #include +#include struct Window; namespace n64 { struct Core { - ~Core() = default; + ~Core() { Stop(); } Core(); void Stop(); void Reset(); + void Step(); void LoadROM(const std::string&); void Run(Window&); void UpdateController(const u8*); void TogglePause() { pause = !pause; } VI& GetVI() { return mem.mmio.vi; } + + void DebuggerInit(); + void DebuggerTick() const; + void DebuggerBreakpointHit(); + void DebuggerCleanup() const; + bool pause = true; bool romLoaded = false; SDL_GameController* gamepad; bool gamepadConnected = false; + DebuggerState debuggerState; bool done = false; std::string rom; -private: - friend struct ::Window; Mem mem; Cpu cpu; };