mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-11 16:46:16 +00:00
Refactor GDB stub (#88)
The intention of this PR is not yet to extend/fix the GDB functionality, but rather to get rid the `mini-gdbstub` dependency. Its functionality should have been reimplemented as closely as possible. The main reason for this is better portability and security fixes (there are numerous OOB read/write vulnerabilities in `mini-gdbstub`)
This commit is contained in:
1
deps/CMakeLists.txt
vendored
1
deps/CMakeLists.txt
vendored
@@ -11,6 +11,5 @@ target_include_directories(reflect INTERFACE
|
||||
|
||||
##########################################
|
||||
|
||||
include(mini-gdbstub.cmake)
|
||||
include(googletest.cmake)
|
||||
include(zlib.cmake)
|
||||
|
||||
1
deps/mini-gdbstub
vendored
1
deps/mini-gdbstub
vendored
Submodule deps/mini-gdbstub deleted from 632ebd3892
11
deps/mini-gdbstub.cmake
vendored
11
deps/mini-gdbstub.cmake
vendored
@@ -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"
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<const socket*> 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<std::chrono::milliseconds>(duration));
|
||||
return this->sleep(std::chrono::duration_cast<std::chrono::milliseconds>(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<const socket*>& 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<const socket*>& sockets, const std::chrono::milliseconds timeout,
|
||||
const bool in_poll)
|
||||
{
|
||||
std::vector<pollfd> 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<const socket*>& 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<std::chrono::milliseconds>(duration));
|
||||
return sleep_sockets(sockets, std::chrono::duration_cast<std::chrono::milliseconds>(duration), in_poll);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<const socket*>& sockets, std::chrono::milliseconds timeout);
|
||||
bool is_ready(bool in_poll) const;
|
||||
|
||||
static bool sleep_sockets(const std::span<const socket*>& sockets, std::chrono::milliseconds timeout,
|
||||
bool in_poll);
|
||||
static bool sleep_sockets_until(const std::span<const socket*>& 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();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<const char*>(data), static_cast<send_size>(size), 0);
|
||||
return static_cast<size_t>(res) == size;
|
||||
return this->send(std::string_view(static_cast<const char*>(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<int>(sizeof(buffer)), 0);
|
||||
if (result == SOCKET_ERROR)
|
||||
while (!data.empty())
|
||||
{
|
||||
return false;
|
||||
const auto res = ::send(this->get_socket(), data.data(), static_cast<send_size>(data.size()), 0);
|
||||
if (res < 0)
|
||||
{
|
||||
if (GET_SOCKET_ERROR() != SERR(EWOULDBLOCK))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
this->sleep(std::chrono::milliseconds(10), true);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (static_cast<size_t>(res) > data.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
data = data.substr(res);
|
||||
}
|
||||
|
||||
data.assign(buffer, buffer + result);
|
||||
return true;
|
||||
return data.empty();
|
||||
}
|
||||
|
||||
std::optional<std::string> tcp_client_socket::receive(const std::optional<size_t> 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<int>(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<address> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<std::string> receive(std::optional<size_t> max_size = std::nullopt);
|
||||
|
||||
std::optional<address> get_target() const;
|
||||
|
||||
|
||||
@@ -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<const char*>(data), static_cast<send_size>(size), 0,
|
||||
&target.get_addr(), target.get_size());
|
||||
return static_cast<size_t>(res) == size;
|
||||
return this->send(target, std::string_view(static_cast<const char*>(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<send_size>(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<size_t>(res) == data.size();
|
||||
}
|
||||
}
|
||||
|
||||
bool udp_socket::receive(address& source, std::string& data) const
|
||||
std::optional<std::pair<address, std::string>> 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<int>(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)}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<std::pair<address, std::string>> receive() const;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#define DECLSPEC_ALIGN(n) alignas(n)
|
||||
#define fopen_s fopen
|
||||
#define sscanf_s sscanf
|
||||
|
||||
#define RESTRICTED_POINTER __restrict
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <ranges>
|
||||
#include <cstddef>
|
||||
#include <cwctype>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -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<std::byte>(0xF);
|
||||
|
||||
if (value <= static_cast<std::byte>(9))
|
||||
{
|
||||
return static_cast<char>('0' + static_cast<char>(value));
|
||||
}
|
||||
|
||||
return static_cast<char>((uppercase ? 'A' : 'a') + (static_cast<char>(value) - 0xA));
|
||||
}
|
||||
|
||||
inline std::pair<char, char> 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<const std::byte*>(data)[i];
|
||||
const auto [high, low] = to_hex(value, uppercase);
|
||||
result.push_back(high);
|
||||
result.push_back(low);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Integer>
|
||||
requires(std::is_integral_v<Integer>)
|
||||
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<const std::byte*>(&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 <typename Integer>
|
||||
requires(std::is_integral_v<Integer>)
|
||||
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<std::byte> 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<std::byte>(lower - '0');
|
||||
}
|
||||
|
||||
if (lower >= 'a' && lower <= 'f')
|
||||
{
|
||||
return static_cast<std::byte>(lower - 'a');
|
||||
}
|
||||
|
||||
return static_cast<std::byte>(0);
|
||||
}
|
||||
|
||||
inline std::vector<std::byte> from_hex_string(const std::string_view str)
|
||||
{
|
||||
const auto size = str.size() / 2;
|
||||
|
||||
std::vector<std::byte> 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;
|
||||
}
|
||||
}
|
||||
|
||||
21
src/gdb-stub/CMakeLists.txt
Normal file
21
src/gdb-stub/CMakeLists.txt
Normal file
@@ -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)
|
||||
81
src/gdb-stub/async_handler.cpp
Normal file
81
src/gdb-stub/async_handler.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "async_handler.hpp"
|
||||
#include <utils/finally.hpp>
|
||||
|
||||
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_);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/gdb-stub/async_handler.hpp
Normal file
38
src/gdb-stub/async_handler.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace gdb_stub
|
||||
{
|
||||
class async_handler
|
||||
{
|
||||
public:
|
||||
using handler = void(std::atomic_bool& should_run);
|
||||
using handler_function = std::function<handler>;
|
||||
|
||||
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();
|
||||
};
|
||||
}
|
||||
19
src/gdb-stub/checksum.hpp
Normal file
19
src/gdb-stub/checksum.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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<uint8_t>(c);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
}
|
||||
65
src/gdb-stub/connection_handler.cpp
Normal file
65
src/gdb-stub/connection_handler.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "connection_handler.hpp"
|
||||
#include "checksum.hpp"
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include <thread>
|
||||
|
||||
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<std::string> 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();
|
||||
}
|
||||
}
|
||||
23
src/gdb-stub/connection_handler.hpp
Normal file
23
src/gdb-stub/connection_handler.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include "stream_processor.hpp"
|
||||
#include <network/tcp_client_socket.hpp>
|
||||
|
||||
namespace gdb_stub
|
||||
{
|
||||
class connection_handler
|
||||
{
|
||||
public:
|
||||
connection_handler(network::tcp_client_socket& client);
|
||||
|
||||
std::optional<std::string> 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_{};
|
||||
};
|
||||
}
|
||||
481
src/gdb-stub/gdb_stub.cpp
Normal file
481
src/gdb-stub/gdb_stub.cpp
Normal file
@@ -0,0 +1,481 @@
|
||||
#include "gdb_stub.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <platform/compiler.hpp>
|
||||
#include <network/tcp_server_socket.hpp>
|
||||
|
||||
#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<std::string_view, std::string_view> 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<target version=\"1.0\"><architecture>" //
|
||||
+ handler.get_target_description() //
|
||||
+ "<architecture>%s</architecture></target>");
|
||||
}
|
||||
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<size_t>(breakpoint_type::END))
|
||||
{
|
||||
return breakpoint_type::software;
|
||||
}
|
||||
|
||||
return static_cast<breakpoint_type>(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<std::byte> 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<std::byte> 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<std::byte> 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<int>(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<std::string>& 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;
|
||||
}
|
||||
}
|
||||
49
src/gdb-stub/gdb_stub.hpp
Normal file
49
src/gdb-stub/gdb_stub.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <network/address.hpp>
|
||||
|
||||
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);
|
||||
}
|
||||
94
src/gdb-stub/stream_processor.cpp
Normal file
94
src/gdb-stub/stream_processor.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "stream_processor.hpp"
|
||||
#include "checksum.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/gdb-stub/stream_processor.hpp
Normal file
22
src/gdb-stub/stream_processor.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
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<std::string> packets_{};
|
||||
|
||||
void process_data_stream();
|
||||
void enqueue_packet(std::string packet);
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
#include "../std_include.hpp"
|
||||
#include "gdb_stub.hpp"
|
||||
|
||||
#include <utils/finally.hpp>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <gdbstub.h>
|
||||
}
|
||||
|
||||
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<gdb_stub_handler*>(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<int>(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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
#include <x64_emulator.hpp>
|
||||
#include "gdb_stub.hpp"
|
||||
#include "scoped_hook.hpp"
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <gdb-stub/gdb_stub.hpp>
|
||||
|
||||
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<breakpoint_key>
|
||||
}
|
||||
};
|
||||
|
||||
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<size_t>(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<size_t>(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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user