diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 168d6006..19217d54 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -11,6 +11,5 @@ target_include_directories(reflect INTERFACE ########################################## -include(mini-gdbstub.cmake) include(googletest.cmake) include(zlib.cmake) diff --git a/deps/mini-gdbstub b/deps/mini-gdbstub deleted file mode 160000 index 632ebd38..00000000 --- a/deps/mini-gdbstub +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 632ebd38921da6aa2cb5cc77c19646df6b48366f diff --git a/deps/mini-gdbstub.cmake b/deps/mini-gdbstub.cmake deleted file mode 100644 index b4ed7b63..00000000 --- a/deps/mini-gdbstub.cmake +++ /dev/null @@ -1,11 +0,0 @@ -file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS - mini-gdbstub/lib/*.c -) - -list(SORT SRC_FILES) - -add_library(mini-gdbstub ${SRC_FILES}) - -target_include_directories(mini-gdbstub PUBLIC - "${CMAKE_CURRENT_LIST_DIR}/mini-gdbstub/include" -) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 24b3d98f..77aef22b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(common) add_subdirectory(emulator) +add_subdirectory(gdb-stub) add_subdirectory(unicorn-emulator) add_subdirectory(windows-emulator) if (NOT MOMO_BUILD_AS_LIBRARY) diff --git a/src/analyzer/CMakeLists.txt b/src/analyzer/CMakeLists.txt index 54c497d1..2fbf8005 100644 --- a/src/analyzer/CMakeLists.txt +++ b/src/analyzer/CMakeLists.txt @@ -15,6 +15,7 @@ target_precompile_headers(analyzer PRIVATE std_include.hpp) target_link_libraries(analyzer PRIVATE reflect windows-emulator + gdb-stub ) set_property(GLOBAL PROPERTY VS_STARTUP_PROJECT analyzer) diff --git a/src/analyzer/main.cpp b/src/analyzer/main.cpp index d33d82ea..ef180990 100644 --- a/src/analyzer/main.cpp +++ b/src/analyzer/main.cpp @@ -52,7 +52,7 @@ namespace win_emu.log.print(color::pink, "Waiting for GDB connection on %s...\n", address); win_x64_gdb_stub_handler handler{win_emu}; - run_gdb_stub(handler, "i386:x86-64", gdb_registers.size(), address); + gdb_stub::run_gdb_stub(network::address{"0.0.0.0:28960", AF_INET}, handler); } else { @@ -181,7 +181,7 @@ namespace for (int i = 1; i < argc; ++i) { - args.push_back(argv[i]); + args.emplace_back(argv[i]); } return args; diff --git a/src/common/network/address.hpp b/src/common/network/address.hpp index dbf11e18..d42604b0 100644 --- a/src/common/network/address.hpp +++ b/src/common/network/address.hpp @@ -48,6 +48,14 @@ namespace network address(const sockaddr_in6& addr); address(const sockaddr* addr, socklen_t length); + address(const address&) = default; + address(address&&) noexcept = default; + + address& operator=(const address&) = default; + address& operator=(address&&) noexcept = default; + + ~address() = default; + void set_ipv4(uint32_t ip); void set_ipv4(const in_addr& addr); void set_ipv6(const in6_addr& addr); diff --git a/src/common/network/socket.cpp b/src/common/network/socket.cpp index 3339d3f4..1e057da7 100644 --- a/src/common/network/socket.cpp +++ b/src/common/network/socket.cpp @@ -31,7 +31,7 @@ namespace network socket::~socket() { - this->release(); + this->close(); } socket::socket(socket&& obj) noexcept @@ -43,7 +43,7 @@ namespace network { if (this != &obj) { - this->release(); + this->close(); this->socket_ = obj.socket_; obj.socket_ = INVALID_SOCKET; @@ -53,15 +53,20 @@ namespace network } socket::operator bool() const + { + return this->is_valid(); + } + + bool socket::is_valid() const { return this->socket_ != INVALID_SOCKET; } - void socket::release() + void socket::close() { if (this->socket_ != INVALID_SOCKET) { - closesocket(this->socket_); + ::closesocket(this->socket_); this->socket_ = INVALID_SOCKET; } } @@ -90,7 +95,7 @@ namespace network #endif } - bool socket::sleep(const std::chrono::milliseconds timeout) const + bool socket::sleep(const std::chrono::milliseconds timeout, const bool in_poll) const { /*fd_set fdr; FD_ZERO(&fdr); @@ -119,13 +124,13 @@ namespace network std::vector sockets{}; sockets.push_back(this); - return sleep_sockets(sockets, timeout); + return sleep_sockets(sockets, timeout, in_poll); } - bool socket::sleep_until(const std::chrono::high_resolution_clock::time_point time_point) const + bool socket::sleep_until(const std::chrono::high_resolution_clock::time_point time_point, const bool in_poll) const { const auto duration = time_point - std::chrono::high_resolution_clock::now(); - return this->sleep(std::chrono::duration_cast(duration)); + return this->sleep(std::chrono::duration_cast(duration), in_poll); } SOCKET socket::get_socket() const @@ -167,7 +172,13 @@ namespace network return address->get_addr().sa_family; } - bool socket::sleep_sockets(const std::span& sockets, const std::chrono::milliseconds timeout) + bool socket::is_ready(const bool in_poll) const + { + return this->is_valid() && is_socket_ready(this->socket_, in_poll); + } + + bool socket::sleep_sockets(const std::span& sockets, const std::chrono::milliseconds timeout, + const bool in_poll) { std::vector pfds{}; pfds.resize(sockets.size()); @@ -178,7 +189,7 @@ namespace network const auto& socket = sockets[i]; pfd.fd = socket->get_socket(); - pfd.events = POLLIN; + pfd.events = in_poll ? POLLIN : POLLOUT; pfd.revents = 0; } @@ -223,9 +234,10 @@ namespace network } bool socket::sleep_sockets_until(const std::span& sockets, - const std::chrono::high_resolution_clock::time_point time_point) + const std::chrono::high_resolution_clock::time_point time_point, + const bool in_poll) { const auto duration = time_point - std::chrono::high_resolution_clock::now(); - return sleep_sockets(sockets, std::chrono::duration_cast(duration)); + return sleep_sockets(sockets, std::chrono::duration_cast(duration), in_poll); } } diff --git a/src/common/network/socket.hpp b/src/common/network/socket.hpp index 4696fb88..957c9c1b 100644 --- a/src/common/network/socket.hpp +++ b/src/common/network/socket.hpp @@ -10,7 +10,7 @@ using send_size = int; #define GET_SOCKET_ERROR() (WSAGetLastError()) #define poll WSAPoll -#define SOCK_WOULDBLOCK WSAEWOULDBLOCK +#define SERR(x) (WSA##x) #define SHUT_RDWR SD_BOTH #else using SOCKET = int; @@ -19,7 +19,7 @@ using send_size = size_t; #define SOCKET_ERROR (-1) #define GET_SOCKET_ERROR() (errno) #define closesocket close -#define SOCK_WOULDBLOCK EWOULDBLOCK +#define SERR(x) (x) #endif namespace network @@ -42,14 +42,16 @@ namespace network operator bool() const; + bool is_valid() const; + bool bind(const address& target); bool set_blocking(bool blocking); static bool set_blocking(SOCKET s, bool blocking); static constexpr bool socket_is_ready = true; - bool sleep(std::chrono::milliseconds timeout) const; - bool sleep_until(std::chrono::high_resolution_clock::time_point time_point) const; + bool sleep(std::chrono::milliseconds timeout, bool in_poll = true) const; + bool sleep_until(std::chrono::high_resolution_clock::time_point time_point, bool in_poll = true) const; SOCKET get_socket() const; uint16_t get_port() const; @@ -57,15 +59,18 @@ namespace network int get_address_family() const; - static bool sleep_sockets(const std::span& sockets, std::chrono::milliseconds timeout); + bool is_ready(bool in_poll) const; + + static bool sleep_sockets(const std::span& sockets, std::chrono::milliseconds timeout, + bool in_poll); static bool sleep_sockets_until(const std::span& sockets, - std::chrono::high_resolution_clock::time_point time_point); + std::chrono::high_resolution_clock::time_point time_point, bool in_poll); static bool is_socket_ready(SOCKET s, bool in_poll); + void close(); + private: SOCKET socket_ = INVALID_SOCKET; - - void release(); }; } diff --git a/src/common/network/tcp_client_socket.cpp b/src/common/network/tcp_client_socket.cpp index 9739b394..fc5921f1 100644 --- a/src/common/network/tcp_client_socket.cpp +++ b/src/common/network/tcp_client_socket.cpp @@ -26,27 +26,53 @@ namespace network bool tcp_client_socket::send(const void* data, const size_t size) const { - const auto res = ::send(this->get_socket(), static_cast(data), static_cast(size), 0); - return static_cast(res) == size; + return this->send(std::string_view(static_cast(data), size)); } - bool tcp_client_socket::send(const std::string_view data) const + bool tcp_client_socket::send(std::string_view data) const { - return this->send(data.data(), data.size()); - } - - bool tcp_client_socket::receive(std::string& data) const - { - char buffer[0x2000]; - - const auto result = recv(this->get_socket(), buffer, static_cast(sizeof(buffer)), 0); - if (result == SOCKET_ERROR) + while (!data.empty()) { - return false; + const auto res = ::send(this->get_socket(), data.data(), static_cast(data.size()), 0); + if (res < 0) + { + if (GET_SOCKET_ERROR() != SERR(EWOULDBLOCK)) + { + break; + } + + this->sleep(std::chrono::milliseconds(10), true); + continue; + } + + if (static_cast(res) > data.size()) + { + break; + } + + data = data.substr(res); } - data.assign(buffer, buffer + result); - return true; + return data.empty(); + } + + std::optional tcp_client_socket::receive(const std::optional max_size) + { + char buffer[0x2000]; + const auto size = std::min(sizeof(buffer), max_size.value_or(sizeof(buffer))); + + const auto result = recv(this->get_socket(), buffer, static_cast(size), 0); + if (result > 0) + { + return std::string(buffer, result); + } + + if (result == 0 || (result < 0 && GET_SOCKET_ERROR() == SERR(ECONNRESET))) + { + this->close(); + } + + return std::nullopt; } std::optional
tcp_client_socket::get_target() const @@ -69,6 +95,6 @@ namespace network } const auto error = GET_SOCKET_ERROR(); - return error == SOCK_WOULDBLOCK; + return error == SERR(EWOULDBLOCK); } } diff --git a/src/common/network/tcp_client_socket.hpp b/src/common/network/tcp_client_socket.hpp index e5b586a1..187829be 100644 --- a/src/common/network/tcp_client_socket.hpp +++ b/src/common/network/tcp_client_socket.hpp @@ -21,7 +21,7 @@ namespace network [[maybe_unused]] bool send(const void* data, size_t size) const; [[maybe_unused]] bool send(std::string_view data) const; - bool receive(std::string& data) const; + std::optional receive(std::optional max_size = std::nullopt); std::optional
get_target() const; diff --git a/src/common/network/udp_socket.cpp b/src/common/network/udp_socket.cpp index 99c6e9e6..379ccbab 100644 --- a/src/common/network/udp_socket.cpp +++ b/src/common/network/udp_socket.cpp @@ -9,29 +9,39 @@ namespace network bool udp_socket::send(const address& target, const void* data, const size_t size) const { - const auto res = sendto(this->get_socket(), static_cast(data), static_cast(size), 0, - &target.get_addr(), target.get_size()); - return static_cast(res) == size; + return this->send(target, std::string_view(static_cast(data), size)); } bool udp_socket::send(const address& target, const std::string_view data) const { - return this->send(target, data.data(), data.size()); + while (true) + { + const auto res = sendto(this->get_socket(), data.data(), static_cast(data.size()), 0, + &target.get_addr(), target.get_size()); + + if (res < 0 && GET_SOCKET_ERROR() == SERR(EWOULDBLOCK)) + { + this->sleep(std::chrono::milliseconds(10), true); + continue; + } + + return static_cast(res) == data.size(); + } } - bool udp_socket::receive(address& source, std::string& data) const + std::optional> udp_socket::receive() const { char buffer[0x2000]; + address source{}; auto len = source.get_max_size(); const auto result = recvfrom(this->get_socket(), buffer, static_cast(sizeof(buffer)), 0, &source.get_addr(), &len); if (result == SOCKET_ERROR) { - return false; + return std::nullopt; } - data.assign(buffer, buffer + result); - return true; + return {{source, std::string(buffer, result)}}; } } diff --git a/src/common/network/udp_socket.hpp b/src/common/network/udp_socket.hpp index 2e240728..affbe6c8 100644 --- a/src/common/network/udp_socket.hpp +++ b/src/common/network/udp_socket.hpp @@ -17,6 +17,6 @@ namespace network [[maybe_unused]] bool send(const address& target, const void* data, size_t size) const; [[maybe_unused]] bool send(const address& target, std::string_view data) const; - bool receive(address& source, std::string& data) const; + std::optional> receive() const; }; } diff --git a/src/common/platform/compiler.hpp b/src/common/platform/compiler.hpp index eda34323..34c1cc4d 100644 --- a/src/common/platform/compiler.hpp +++ b/src/common/platform/compiler.hpp @@ -28,6 +28,7 @@ #define DECLSPEC_ALIGN(n) alignas(n) #define fopen_s fopen +#define sscanf_s sscanf #define RESTRICTED_POINTER __restrict diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index a1c6497e..2b5100b8 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -1,6 +1,7 @@ #pragma once +#include #include -#include +#include #include #include @@ -44,4 +45,108 @@ namespace utils::string { return to_lower(std::move(str)); } + + inline char to_nibble(std::byte value, const bool uppercase = false) + { + value = value & static_cast(0xF); + + if (value <= static_cast(9)) + { + return static_cast('0' + static_cast(value)); + } + + return static_cast((uppercase ? 'A' : 'a') + (static_cast(value) - 0xA)); + } + + inline std::pair to_hex(const std::byte value, const bool uppercase = false) + { + return {to_nibble(value >> 4, uppercase), to_nibble(value, uppercase)}; + } + + inline std::string to_hex_string(const void* data, const size_t size, const bool uppercase = false) + { + std::string result{}; + result.reserve(size * 2); + + for (size_t i = 0; i < size; ++i) + { + const auto value = static_cast(data)[i]; + const auto [high, low] = to_hex(value, uppercase); + result.push_back(high); + result.push_back(low); + } + + return result; + } + + template + requires(std::is_integral_v) + std::string to_hex_number(const Integer& i, const bool uppercase = false) + { + std::string res{}; + res.reserve(sizeof(i) * 2); + + const std::span data{reinterpret_cast(&i), sizeof(i)}; + + for (const auto value : data) + { + const auto [high, low] = to_hex(value, uppercase); + res.insert(res.begin(), {high, low}); + } + + while (res.size() > 1 && res.front() == '0') + { + res.erase(res.begin()); + } + + return res; + } + + template + requires(std::is_integral_v) + std::string to_hex_string(const Integer& i, const bool uppercase = false) + { + return to_hex_string(&i, sizeof(Integer), uppercase); + } + + inline std::string to_hex_string(const std::span data, const bool uppercase = false) + { + return to_hex_string(data.data(), data.size(), uppercase); + } + + inline std::byte parse_nibble(const char nibble) + { + const auto lower = char_to_lower(nibble); + + if (lower >= '0' && lower <= '9') + { + return static_cast(lower - '0'); + } + + if (lower >= 'a' && lower <= 'f') + { + return static_cast(lower - 'a'); + } + + return static_cast(0); + } + + inline std::vector from_hex_string(const std::string_view str) + { + const auto size = str.size() / 2; + + std::vector data{}; + data.reserve(size); + + for (size_t i = 0; i < size; ++i) + { + const auto high = parse_nibble(str[i * 2 + 0]); + const auto low = parse_nibble(str[i * 2 + 1]); + const auto value = (high << 4) | low; + + data.push_back(value); + } + + return data; + } } diff --git a/src/gdb-stub/CMakeLists.txt b/src/gdb-stub/CMakeLists.txt new file mode 100644 index 00000000..2b3e2d80 --- /dev/null +++ b/src/gdb-stub/CMakeLists.txt @@ -0,0 +1,21 @@ +file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS + *.cpp + *.hpp + *.rc +) + +list(SORT SRC_FILES) + +add_library(gdb-stub ${SRC_FILES}) + +momo_assign_source_group(${SRC_FILES}) + +target_link_libraries(gdb-stub PRIVATE + emulator-common +) + +target_include_directories(gdb-stub INTERFACE + "${CMAKE_CURRENT_LIST_DIR}" +) + +momo_strip_target(gdb-stub) diff --git a/src/gdb-stub/async_handler.cpp b/src/gdb-stub/async_handler.cpp new file mode 100644 index 00000000..9560a3b3 --- /dev/null +++ b/src/gdb-stub/async_handler.cpp @@ -0,0 +1,81 @@ +#include "async_handler.hpp" +#include + +using namespace std::chrono_literals; + +namespace gdb_stub +{ + async_handler::async_handler(handler_function h) + : handler_(std::move(h)) + { + this->stop_ = false; + this->runner_ = std::thread([this] { + this->work(); // + }); + } + + async_handler::~async_handler() + { + this->stop_ = true; + this->run_ = false; + + if (this->runner_.joinable()) + { + this->runner_.join(); + } + } + + void async_handler::pause() + { + this->run_ = false; + + while (this->is_running_ && !this->stop_) + { + std::this_thread::sleep_for(1ms); + } + } + + void async_handler::run() + { + if (this->stop_) + { + return; + } + + this->run_ = true; + + while (!this->is_running_ && !this->stop_) + { + std::this_thread::sleep_for(1ms); + } + } + + bool async_handler::is_running() const + { + return this->is_running_; + } + + void async_handler::work() + { + while (true) + { + while (!this->run_ && !this->stop_) + { + this->is_running_ = false; + std::this_thread::sleep_for(10ms); + } + + if (this->stop_) + { + break; + } + + const auto _ = utils::finally([this] { + this->is_running_ = false; // + }); + + this->is_running_ = true; + this->handler_(this->run_); + } + } +} diff --git a/src/gdb-stub/async_handler.hpp b/src/gdb-stub/async_handler.hpp new file mode 100644 index 00000000..608fdd66 --- /dev/null +++ b/src/gdb-stub/async_handler.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include +#include + +namespace gdb_stub +{ + class async_handler + { + public: + using handler = void(std::atomic_bool& should_run); + using handler_function = std::function; + + async_handler(handler_function handler); + ~async_handler(); + + async_handler(async_handler&&) = delete; + async_handler(const async_handler&) = delete; + + async_handler& operator=(async_handler&&) = delete; + async_handler& operator=(const async_handler&) = delete; + + void run(); + void pause(); + bool is_running() const; + + private: + std::atomic_bool run_{false}; + std::atomic_bool stop_{false}; + std::atomic_bool is_running_{false}; + + handler_function handler_{}; + std::thread runner_{}; + + void work(); + }; +} diff --git a/src/gdb-stub/checksum.hpp b/src/gdb-stub/checksum.hpp new file mode 100644 index 00000000..cd2c0a71 --- /dev/null +++ b/src/gdb-stub/checksum.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace gdb_stub +{ + constexpr size_t CHECKSUM_SIZE = 2; + + inline uint8_t compute_checksum(const std::string_view data) + { + uint8_t checksum = 0; + for (const auto c : data) + { + checksum += static_cast(c); + } + + return checksum; + } +} diff --git a/src/gdb-stub/connection_handler.cpp b/src/gdb-stub/connection_handler.cpp new file mode 100644 index 00000000..9ef40d21 --- /dev/null +++ b/src/gdb-stub/connection_handler.cpp @@ -0,0 +1,65 @@ +#include "connection_handler.hpp" +#include "checksum.hpp" +#include + +#include + +using namespace std::literals; + +namespace gdb_stub +{ + namespace + { + bool read_from_socket(stream_processor& processor, network::tcp_client_socket& client) + { + const auto data = client.receive(); + if (!data) + { + return false; + } + + processor.push_stream_data(*data); + return true; + } + } + + connection_handler::connection_handler(network::tcp_client_socket& client) + : client_(client) + { + this->client_.set_blocking(false); + } + + std::optional connection_handler::get_packet() + { + while (this->client_.is_valid() && !this->processor_.has_packet()) + { + if (!read_from_socket(this->processor_, this->client_)) + { + std::this_thread::sleep_for(100ms); + } + } + + if (this->processor_.has_packet()) + { + return this->processor_.get_next_packet(); + } + + return std::nullopt; + } + + void connection_handler::send_reply(const std::string_view data) const + { + const auto checksum = utils::string::to_hex_string(compute_checksum(data)); + this->send_raw_data("$" + std::string(data) + "#" + checksum); + } + + void connection_handler::send_raw_data(const std::string_view data) const + { + (void)this->client_.send(data); + } + + void connection_handler::close() const + { + this->client_.close(); + } +} diff --git a/src/gdb-stub/connection_handler.hpp b/src/gdb-stub/connection_handler.hpp new file mode 100644 index 00000000..afd326ca --- /dev/null +++ b/src/gdb-stub/connection_handler.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "stream_processor.hpp" +#include + +namespace gdb_stub +{ + class connection_handler + { + public: + connection_handler(network::tcp_client_socket& client); + + std::optional get_packet(); + + void send_reply(std::string_view data) const; + void send_raw_data(std::string_view data) const; + + void close() const; + + private: + network::tcp_client_socket& client_; + stream_processor processor_{}; + }; +} diff --git a/src/gdb-stub/gdb_stub.cpp b/src/gdb-stub/gdb_stub.cpp new file mode 100644 index 00000000..0ebabd8c --- /dev/null +++ b/src/gdb-stub/gdb_stub.cpp @@ -0,0 +1,481 @@ +#include "gdb_stub.hpp" + +#include +#include + +#include +#include +#include + +#include "async_handler.hpp" +#include "connection_handler.hpp" + +using namespace std::literals; + +namespace gdb_stub +{ + namespace + { + void rt_assert(const bool condition) + { + (void)condition; + assert(condition); + } + + network::tcp_client_socket accept_client(const network::address& bind_address) + { + network::tcp_server_socket server{bind_address.get_family()}; + if (!server.bind(bind_address)) + { + return false; + } + + return server.accept(); + } + + std::pair split_string(const std::string_view payload, const char separator) + { + auto name = payload; + std::string_view args{}; + + const auto separator_pos = payload.find_first_of(separator); + if (separator_pos != std::string_view::npos) + + { + name = payload.substr(0, separator_pos); + args = payload.substr(separator_pos + 1); + } + + return {name, args}; + } + + void process_xfer(const connection_handler& connection, debugging_handler& handler, + const std::string_view payload) + { + auto [name, args] = split_string(payload, ':'); + + if (name == "features") + { + connection.send_reply("l" // + + handler.get_target_description() // + + "%s"); + } + else + { + connection.send_reply({}); + } + } + + void process_query(const connection_handler& connection, debugging_handler& handler, + const std::string_view payload) + { + const auto [name, args] = split_string(payload, ':'); + + if (name == "Supported") + { + connection.send_reply("PacketSize=1024;qXfer:features:read+"); + } + else if (name == "Attached") + { + connection.send_reply("1"); + } + else if (name == "Xfer") + { + process_xfer(connection, handler, args); + } + else if (name == "Symbol") + { + connection.send_reply("OK"); + } + else + { + connection.send_reply({}); + } + } + + void process_action(const connection_handler& connection, const action a) + { + if (a == action::shutdown) + { + connection.close(); + } + } + + breakpoint_type translate_breakpoint_type(const uint32_t type) + { + if (type >= static_cast(breakpoint_type::END)) + { + return breakpoint_type::software; + } + + return static_cast(type); + } + + bool change_breakpoint(debugging_handler& handler, const bool set, const breakpoint_type type, + const uint64_t address, const size_t size) + { + if (set) + { + return handler.set_breakpoint(type, address, size); + } + + return handler.delete_breakpoint(type, address, size); + } + + void handle_breakpoint(const connection_handler& connection, debugging_handler& handler, + const std::string& data, const bool set) + { + uint32_t type{}; + uint64_t addr{}; + size_t kind{}; + rt_assert(sscanf_s(data.c_str(), "%x,%" PRIX64 ",%zx", &type, &addr, &kind) == 3); + + const auto res = change_breakpoint(handler, set, translate_breakpoint_type(type), addr, kind); + connection.send_reply(res ? "OK" : "E01"); + } + + void handle_v_packet(const connection_handler& connection, const std::string_view data) + { + const auto [name, args] = split_string(data, ':'); + + if (name == "Cont?") + { + // IDA pro gets confused if the reply arrives too early :( + std::this_thread::sleep_for(1s); + + connection.send_reply("vCont;s;c;"); + } + else + { + connection.send_reply({}); + } + } + + void read_registers(const connection_handler& connection, debugging_handler& handler) + { + std::string response{}; + std::vector data{}; + data.resize(handler.get_max_register_size()); + + const auto registers = handler.get_register_count(); + + for (size_t i = 0; i < registers; ++i) + { + memset(data.data(), 0, data.size()); + const auto res = handler.read_register(i, data.data(), data.size()); + + if (!res) + { + connection.send_reply("E01"); + return; + } + + response.append(utils::string::to_hex_string(data)); + } + + connection.send_reply(response); + } + + void write_registers(const connection_handler& connection, debugging_handler& handler, + const std::string_view payload) + { + const auto data = utils::string::from_hex_string(payload); + + const auto registers = handler.get_register_count(); + const auto register_size = handler.get_max_register_size(); + + for (size_t i = 0; i < registers; ++i) + { + const auto offset = i * register_size; + const auto end_offset = offset + register_size; + + if (data.size() < end_offset) + { + connection.send_reply("E01"); + return; + } + + const auto res = handler.write_register(i, data.data() + offset, register_size); + + if (!res) + { + connection.send_reply("E01"); + return; + } + } + + connection.send_reply("OK"); + } + + void read_single_register(const connection_handler& connection, debugging_handler& handler, + const std::string& payload) + { + size_t reg{}; + rt_assert(sscanf_s(payload.c_str(), "%zx", ®) == 1); + + std::vector data{}; + data.resize(handler.get_max_register_size()); + + const auto res = handler.read_register(reg, data.data(), data.size()); + + if (res) + { + connection.send_reply(utils::string::to_hex_string(data)); + } + else + { + connection.send_reply("E01"); + } + } + + void write_single_register(const connection_handler& connection, debugging_handler& handler, + const std::string_view payload) + { + const auto [reg, hex_data] = split_string(payload, '='); + + size_t register_index{}; + rt_assert(sscanf_s(std::string(reg).c_str(), "%zx", ®ister_index) == 1); + + const auto register_size = handler.get_max_register_size(); + const auto data = utils::string::from_hex_string(hex_data); + + const auto res = register_size <= data.size() && // + handler.write_register(register_index, data.data(), register_size); + + connection.send_reply(res ? "OK" : "E01"); + } + + void read_memory(const connection_handler& connection, debugging_handler& handler, const std::string& payload) + { + uint64_t address{}; + size_t size{}; + rt_assert(sscanf_s(payload.c_str(), "%" PRIx64 ",%zx", &address, &size) == 2); + + if (size > 0x1000) + { + connection.send_reply("E01"); + return; + } + + std::vector data{}; + data.resize(size); + + const auto res = handler.read_memory(address, data.data(), data.size()); + if (!res) + { + connection.send_reply("E01"); + return; + } + + connection.send_reply(utils::string::to_hex_string(data)); + } + + void write_memory(const connection_handler& connection, debugging_handler& handler, + const std::string_view payload) + { + const auto [info, hex_data] = split_string(payload, ':'); + + size_t size{}; + uint64_t address{}; + rt_assert(sscanf_s(std::string(info).c_str(), "%" PRIx64 ",%zx", &address, &size) == 2); + + if (size > 0x1000) + { + connection.send_reply("E01"); + return; + } + + auto data = utils::string::from_hex_string(hex_data); + data.resize(size); + + const auto res = handler.write_memory(address, data.data(), data.size()); + connection.send_reply(res ? "OK" : "E01"); + } + + std::string decode_x_memory(const std::string_view payload) + { + std::string result{}; + result.reserve(payload.size()); + + bool xor_next = false; + + for (auto value : payload) + { + if (xor_next) + { + value ^= 0x20; + xor_next = false; + } + else if (value == '}') + { + xor_next = true; + continue; + } + + result.push_back(value); + } + + return result; + } + + void write_x_memory(const connection_handler& connection, debugging_handler& handler, + const std::string_view payload) + { + const auto [info, encoded_data] = split_string(payload, ':'); + + size_t size{}; + uint64_t address{}; + rt_assert(sscanf_s(std::string(info).c_str(), "%" PRIx64 ",%zx", &address, &size) == 2); + + if (size > 0x1000) + { + connection.send_reply("E01"); + return; + } + + auto data = decode_x_memory(encoded_data); + data.resize(size); + + const auto res = handler.write_memory(address, data.data(), data.size()); + if (!res) + { + connection.send_reply("E01"); + return; + } + + connection.send_reply("OK"); + } + + void handle_command(const connection_handler& connection, async_handler& async, debugging_handler& handler, + const uint8_t command, const std::string_view data) + { + // printf("GDB command: %c -> %.*s\n", command, static_cast(data.size()), data.data()); + + switch (command) + { + case 'c': + async.run(); + process_action(connection, handler.run()); + async.pause(); + connection.send_reply("S05"); + break; + + case 's': + process_action(connection, handler.singlestep()); + connection.send_reply("S05"); + break; + + case 'q': + process_query(connection, handler, data); + break; + + case 'D': + connection.close(); + break; + + case 'z': + case 'Z': + handle_breakpoint(connection, handler, std::string(data), command == 'Z'); + break; + + case '?': + connection.send_reply("S05"); + break; + + case 'v': + handle_v_packet(connection, data); + break; + + case 'g': + read_registers(connection, handler); + break; + + case 'G': + write_registers(connection, handler, data); + break; + + case 'p': + read_single_register(connection, handler, std::string(data)); + break; + + case 'P': + write_single_register(connection, handler, data); + break; + + case 'm': + read_memory(connection, handler, std::string(data)); + break; + + case 'M': + write_memory(connection, handler, data); + break; + + case 'X': + write_x_memory(connection, handler, data); + break; + + default: + connection.send_reply({}); + break; + } + } + + void process_packet(const connection_handler& connection, async_handler& async, debugging_handler& handler, + const std::string_view packet) + { + connection.send_raw_data("+"); + + if (packet.empty()) + { + return; + } + + const auto command = packet.front(); + handle_command(connection, async, handler, command, packet.substr(1)); + } + + bool is_interrupt_packet(const std::optional& data) + { + return data && data->size() == 1 && data->front() == '\x03'; + } + } + + bool run_gdb_stub(const network::address& bind_address, debugging_handler& handler) + { + auto client = accept_client(bind_address); + if (!client) + { + return false; + } + + async_handler async{[&](std::atomic_bool& can_run) { + while (can_run) + { + std::this_thread::sleep_for(10ms); + + const auto data = client.receive(1); + + if (is_interrupt_packet(data) || !client.is_valid()) + { + handler.on_interrupt(); + can_run = false; + } + } + }}; + + connection_handler connection{client}; + + while (true) + { + const auto packet = connection.get_packet(); + if (!packet) + { + break; + } + + process_packet(connection, async, handler, *packet); + } + + return true; + } +} diff --git a/src/gdb-stub/gdb_stub.hpp b/src/gdb-stub/gdb_stub.hpp new file mode 100644 index 00000000..9a71b7b2 --- /dev/null +++ b/src/gdb-stub/gdb_stub.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include + +namespace gdb_stub +{ + enum class action : uint8_t + { + none, + resume, + shutdown, + }; + + enum class breakpoint_type : uint8_t + { + software = 0, + hardware_exec = 1, + hardware_write = 2, + hardware_read = 3, + hardware_read_write = 4, + END, + }; + + struct debugging_handler + { + virtual ~debugging_handler() = default; + + virtual action run() = 0; + virtual action singlestep() = 0; + + virtual size_t get_register_count() = 0; + virtual size_t get_max_register_size() = 0; + + virtual bool read_register(size_t reg, void* data, size_t max_length) = 0; + virtual bool write_register(size_t reg, const void* data, size_t size) = 0; + + virtual bool read_memory(uint64_t address, void* data, size_t length) = 0; + virtual bool write_memory(uint64_t address, const void* data, size_t length) = 0; + + virtual bool set_breakpoint(breakpoint_type type, uint64_t address, size_t size) = 0; + virtual bool delete_breakpoint(breakpoint_type type, uint64_t address, size_t size) = 0; + + virtual void on_interrupt() = 0; + + virtual std::string get_target_description() = 0; + }; + + bool run_gdb_stub(const network::address& bind_address, debugging_handler& handler); +} diff --git a/src/gdb-stub/stream_processor.cpp b/src/gdb-stub/stream_processor.cpp new file mode 100644 index 00000000..0dd90d1b --- /dev/null +++ b/src/gdb-stub/stream_processor.cpp @@ -0,0 +1,94 @@ +#include "stream_processor.hpp" +#include "checksum.hpp" + +#include +#include + +namespace gdb_stub +{ + namespace + { + void trim_data_stream_start(std::string& stream) + { + while (!stream.empty() && stream.front() != '$') + { + stream.erase(stream.begin()); + } + } + } + + bool stream_processor::has_packet() const + { + return !this->packets_.empty(); + } + + std::string stream_processor::get_next_packet() + { + if (this->packets_.empty()) + { + throw std::runtime_error("No packet available"); + } + + auto packet = std::move(this->packets_.front()); + this->packets_.pop(); + + return packet; + } + + void stream_processor::push_stream_data(const std::string& data) + { + this->stream_.append(data); + this->process_data_stream(); + } + + void stream_processor::process_data_stream() + { + while (true) + { + trim_data_stream_start(this->stream_); + + const auto end = this->stream_.find_first_of('#'); + if (end == std::string::npos) + { + break; + } + + const auto packet_size = end + CHECKSUM_SIZE + 1; + + if (packet_size > this->stream_.size()) + { + break; + } + + auto packet = this->stream_.substr(0, packet_size); + this->stream_.erase(0, packet_size); + + this->enqueue_packet(std::move(packet)); + } + } + + void stream_processor::enqueue_packet(std::string packet) + { + constexpr auto END_BYTES = CHECKSUM_SIZE + 1; + + if (packet.size() < (END_BYTES + 1) // + || packet.front() != '$' // + || packet[packet.size() - END_BYTES] != '#') + { + return; + } + + const auto checksum = strtoul(packet.c_str() + packet.size() - CHECKSUM_SIZE, nullptr, 16); + assert((checksum & 0xFF) == checksum); + + packet.erase(packet.begin()); + packet.erase(packet.size() - END_BYTES, END_BYTES); + + const auto computed_checksum = compute_checksum(packet); + + if (computed_checksum == checksum) + { + this->packets_.push(std::move(packet)); + } + } +} diff --git a/src/gdb-stub/stream_processor.hpp b/src/gdb-stub/stream_processor.hpp new file mode 100644 index 00000000..c6b48442 --- /dev/null +++ b/src/gdb-stub/stream_processor.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include +#include + +namespace gdb_stub +{ + class stream_processor + { + public: + bool has_packet() const; + std::string get_next_packet(); + void push_stream_data(const std::string& data); + + private: + std::string stream_{}; + std::queue packets_{}; + + void process_data_stream(); + void enqueue_packet(std::string packet); + }; +} diff --git a/src/windows-emulator/CMakeLists.txt b/src/windows-emulator/CMakeLists.txt index b2ffe79c..90cb3af8 100644 --- a/src/windows-emulator/CMakeLists.txt +++ b/src/windows-emulator/CMakeLists.txt @@ -14,7 +14,6 @@ target_precompile_headers(windows-emulator PRIVATE std_include.hpp) target_link_libraries(windows-emulator PRIVATE unicorn-emulator - mini-gdbstub ) target_link_libraries(windows-emulator PUBLIC diff --git a/src/windows-emulator/debugging/gdb_stub.cpp b/src/windows-emulator/debugging/gdb_stub.cpp deleted file mode 100644 index 6d38a08f..00000000 --- a/src/windows-emulator/debugging/gdb_stub.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "../std_include.hpp" -#include "gdb_stub.hpp" - -#include - -extern "C" -{ -#include -} - -namespace -{ - gdb_action_t map_gdb_action(const gdb_action action) - { - switch (action) - { - case gdb_action::none: - return ACT_NONE; - case gdb_action::resume: - return ACT_RESUME; - case gdb_action::shutdown: - return ACT_SHUTDOWN; - } - - throw std::runtime_error("Bad action"); - } - - breakpoint_type map_breakpoint_type(const bp_type_t type) - { - switch (type) - { - case BP_SOFTWARE: - return breakpoint_type::software; - case BP_HARDWARE_EXEC: - return breakpoint_type::hardware_exec; - case BP_HARDWARE_WRITE: - return breakpoint_type::hardware_write; - case BP_HARDWARE_READ: - return breakpoint_type::hardware_read; - case BP_HARDWARE_READ_WRITE: - return breakpoint_type::hardware_read_write; - } - - throw std::runtime_error("Bad breakpoint type"); - } - - gdb_stub_handler& get_handler(void* args) - { - return *static_cast(args); - } - - gdb_action_t cont(void* args) - { - return map_gdb_action(get_handler(args).cont()); - } - - gdb_action_t stepi(void* args) - { - return map_gdb_action(get_handler(args).stepi()); - } - - int read_reg(void* args, const int regno, size_t* value) - { - return get_handler(args).read_reg(regno, value) ? 0 : 1; - } - - int write_reg(void* args, const int regno, const size_t value) - { - return get_handler(args).write_reg(regno, value) ? 0 : 1; - } - - int read_mem(void* args, const size_t addr, const size_t len, void* val) - { - return get_handler(args).read_mem(addr, len, val) ? 0 : 1; - } - - int write_mem(void* args, const size_t addr, const size_t len, void* val) - { - return get_handler(args).write_mem(addr, len, val) ? 0 : 1; - } - - bool set_bp(void* args, const size_t addr, const bp_type_t type, const size_t size) - { - return get_handler(args).set_bp(map_breakpoint_type(type), addr, size); - } - - bool del_bp(void* args, const size_t addr, const bp_type_t type, const size_t size) - { - return get_handler(args).del_bp(map_breakpoint_type(type), addr, size); - } - - void on_interrupt(void* args) - { - get_handler(args).on_interrupt(); - } - - target_ops get_target_ops() - { - target_ops ops{}; - - ops.cont = cont; - ops.stepi = stepi; - ops.read_reg = read_reg; - ops.write_reg = write_reg; - ops.read_mem = read_mem; - ops.write_mem = write_mem; - ops.set_bp = set_bp; - ops.del_bp = del_bp; - ops.on_interrupt = on_interrupt; - - return ops; - } -} - -bool run_gdb_stub(gdb_stub_handler& handler, std::string target_description, const size_t register_count, - std::string bind_address) -{ - const arch_info_t info{ - target_description.data(), - static_cast(register_count), - sizeof(uint64_t), - }; - - auto ops = get_target_ops(); - - gdbstub_t stub{}; - - if (!gdbstub_init(&stub, &ops, info, bind_address.data())) - { - return false; - } - - const auto _ = utils::finally([&] { gdbstub_close(&stub); }); - - return gdbstub_run(&stub, &handler); -} diff --git a/src/windows-emulator/debugging/gdb_stub.hpp b/src/windows-emulator/debugging/gdb_stub.hpp deleted file mode 100644 index de6101f3..00000000 --- a/src/windows-emulator/debugging/gdb_stub.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -enum class gdb_action : uint8_t -{ - none, - resume, - shutdown, -}; - -enum class breakpoint_type : uint8_t -{ - software, - hardware_exec, - hardware_write, - hardware_read, - hardware_read_write, -}; - -struct gdb_stub_handler -{ - virtual ~gdb_stub_handler() = default; - - virtual gdb_action cont() = 0; - virtual gdb_action stepi() = 0; - - virtual bool read_reg(int regno, size_t* value) = 0; - virtual bool write_reg(int regno, size_t value) = 0; - - virtual bool read_mem(size_t addr, size_t len, void* val) = 0; - virtual bool write_mem(size_t addr, size_t len, void* val) = 0; - - virtual bool set_bp(breakpoint_type type, size_t addr, size_t size) = 0; - virtual bool del_bp(breakpoint_type type, size_t addr, size_t size) = 0; - - virtual void on_interrupt() = 0; -}; - -bool run_gdb_stub(gdb_stub_handler& handler, std::string target_description, size_t register_count, - std::string bind_address); diff --git a/src/windows-emulator/debugging/win_x64_gdb_stub_handler.hpp b/src/windows-emulator/debugging/win_x64_gdb_stub_handler.hpp index ce2bc314..96b87212 100644 --- a/src/windows-emulator/debugging/win_x64_gdb_stub_handler.hpp +++ b/src/windows-emulator/debugging/win_x64_gdb_stub_handler.hpp @@ -12,7 +12,7 @@ class win_x64_gdb_stub_handler : public x64_gdb_stub_handler { } - gdb_action cont() override + gdb_stub::action run() override { try { @@ -20,13 +20,13 @@ class win_x64_gdb_stub_handler : public x64_gdb_stub_handler } catch (const std::exception& e) { - puts(e.what()); + this->win_emu_->log.error("%s\n", e.what()); } - return gdb_action::resume; + return gdb_stub::action::resume; } - gdb_action stepi() override + gdb_stub::action singlestep() override { try { @@ -34,10 +34,15 @@ class win_x64_gdb_stub_handler : public x64_gdb_stub_handler } catch (const std::exception& e) { - puts(e.what()); + this->win_emu_->log.error("%s\n", e.what()); } - return gdb_action::resume; + return gdb_stub::action::resume; + } + + std::string get_target_description() override + { + return "i386:x86-64"; } private: diff --git a/src/windows-emulator/debugging/x64_gdb_stub_handler.hpp b/src/windows-emulator/debugging/x64_gdb_stub_handler.hpp index 58e1a47b..f80996cd 100644 --- a/src/windows-emulator/debugging/x64_gdb_stub_handler.hpp +++ b/src/windows-emulator/debugging/x64_gdb_stub_handler.hpp @@ -1,8 +1,8 @@ #pragma once #include -#include "gdb_stub.hpp" #include "scoped_hook.hpp" #include +#include inline std::vector gdb_registers{ x64_register::rax, x64_register::rbx, x64_register::rcx, x64_register::rdx, x64_register::rsi, x64_register::rdi, @@ -16,18 +16,20 @@ inline std::vector gdb_registers{ x64_register::gs,*/ }; -inline memory_operation map_breakpoint_type(const breakpoint_type type) +inline memory_operation map_breakpoint_type(const gdb_stub::breakpoint_type type) { + using enum gdb_stub::breakpoint_type; + switch (type) { - case breakpoint_type::software: - case breakpoint_type::hardware_exec: + case software: + case hardware_exec: return memory_operation::exec; - case breakpoint_type::hardware_read: + case hardware_read: return memory_permission::read; - case breakpoint_type::hardware_write: + case hardware_write: return memory_permission::write; - case breakpoint_type::hardware_read_write: + case hardware_read_write: return memory_permission::read_write; default: throw std::runtime_error("Bad bp type"); @@ -38,7 +40,7 @@ struct breakpoint_key { size_t addr{}; size_t size{}; - breakpoint_type type{}; + gdb_stub::breakpoint_type type{}; bool operator==(const breakpoint_key& other) const { @@ -56,7 +58,7 @@ struct std::hash } }; -class x64_gdb_stub_handler : public gdb_stub_handler +class x64_gdb_stub_handler : public gdb_stub::debugging_handler { public: x64_gdb_stub_handler(x64_emulator& emu) @@ -66,7 +68,7 @@ class x64_gdb_stub_handler : public gdb_stub_handler ~x64_gdb_stub_handler() override = default; - gdb_action cont() override + gdb_stub::action run() override { try { @@ -77,10 +79,10 @@ class x64_gdb_stub_handler : public gdb_stub_handler puts(e.what()); } - return gdb_action::resume; + return gdb_stub::action::resume; } - gdb_action stepi() override + gdb_stub::action singlestep() override { try { @@ -91,39 +93,51 @@ class x64_gdb_stub_handler : public gdb_stub_handler puts(e.what()); } - return gdb_action::resume; + return gdb_stub::action::resume; } - bool read_reg(const int regno, size_t* value) override + size_t get_register_count() override { - *value = 0; + return gdb_registers.size(); + } + size_t get_max_register_size() override + { + // return 256 / 8; + return 64 / 8; + } + + bool read_register(const size_t reg, void* data, const size_t max_length) override + { try { - if (static_cast(regno) >= gdb_registers.size()) + if (reg >= gdb_registers.size()) { + // TODO: Fix return true; } - this->emu_->read_register(gdb_registers[regno], value, sizeof(*value)); + this->emu_->read_register(gdb_registers[reg], data, max_length); return true; } catch (...) { + // TODO: Fix return true; } } - bool write_reg(const int regno, const size_t value) override + bool write_register(const size_t reg, const void* data, const size_t size) override { try { - if (static_cast(regno) >= gdb_registers.size()) + if (reg >= gdb_registers.size()) { + // TODO: Fix return true; } - this->emu_->write_register(gdb_registers[regno], &value, sizeof(value)); + this->emu_->write_register(gdb_registers[reg], data, size); return true; } catch (...) @@ -132,16 +146,16 @@ class x64_gdb_stub_handler : public gdb_stub_handler } } - bool read_mem(const size_t addr, const size_t len, void* val) override + bool read_memory(const uint64_t address, void* data, const size_t length) override { - return this->emu_->try_read_memory(addr, val, len); + return this->emu_->try_read_memory(address, data, length); } - bool write_mem(const size_t addr, const size_t len, void* val) override + bool write_memory(const uint64_t address, const void* data, const size_t length) override { try { - this->emu_->write_memory(addr, val, len); + this->emu_->write_memory(address, data, length); return true; } catch (...) @@ -150,7 +164,7 @@ class x64_gdb_stub_handler : public gdb_stub_handler } } - bool set_bp(const breakpoint_type type, const size_t addr, const size_t size) override + bool set_breakpoint(const gdb_stub::breakpoint_type type, const uint64_t addr, const size_t size) override { try { @@ -170,7 +184,7 @@ class x64_gdb_stub_handler : public gdb_stub_handler } } - bool del_bp(const breakpoint_type type, const size_t addr, const size_t size) override + bool delete_breakpoint(const gdb_stub::breakpoint_type type, const uint64_t addr, const size_t size) override { try { diff --git a/src/windows-emulator/devices/afd_endpoint.cpp b/src/windows-emulator/devices/afd_endpoint.cpp index f0ab1cb0..1a364eb4 100644 --- a/src/windows-emulator/devices/afd_endpoint.cpp +++ b/src/windows-emulator/devices/afd_endpoint.cpp @@ -457,7 +457,7 @@ namespace if (recevied_data < 0) { const auto error = GET_SOCKET_ERROR(); - if (error == SOCK_WOULDBLOCK) + if (error == SERR(EWOULDBLOCK)) { this->delay_ioctrl(c, {}, true); return STATUS_PENDING; @@ -512,7 +512,7 @@ namespace if (sent_data < 0) { const auto error = GET_SOCKET_ERROR(); - if (error == SOCK_WOULDBLOCK) + if (error == SERR(EWOULDBLOCK)) { this->delay_ioctrl(c, {}, false); return STATUS_PENDING;