diff --git a/src/gdb-stub/gdb_stub.cpp b/src/gdb-stub/gdb_stub.cpp index 764fbf14..8d2d48d9 100644 --- a/src/gdb-stub/gdb_stub.cpp +++ b/src/gdb-stub/gdb_stub.cpp @@ -22,6 +22,19 @@ namespace gdb_stub assert(condition); } + struct debugging_state + { + std::optional continuation_thread{}; + }; + + struct debugging_context + { + connection_handler& connection; + debugging_handler& handler; + debugging_state& state; + async_handler& async; + }; + network::tcp_client_socket accept_client(const network::address& bind_address) { network::tcp_server_socket server{bind_address.get_family()}; @@ -49,79 +62,107 @@ namespace gdb_stub return {name, args}; } - void handle_features(connection_handler& connection, debugging_handler& handler, const std::string_view payload) + void send_xfer_data(connection_handler& connection, const std::string& args, const std::string_view data) { - const auto [command, args] = split_string(payload, ':'); - - if (command != "read") - { - connection.send_reply({}); - return; - } - - const auto [file, data] = split_string(args, ':'); - size_t offset{}, length{}; - rt_assert(sscanf_s(std::string(data).c_str(), "%zx,%zx", &offset, &length) == 2); + rt_assert(sscanf_s(args.c_str(), "%zx,%zx", &offset, &length) == 2); - const auto target_description = handler.get_target_description(file); - - if (offset >= target_description.size()) + if (offset >= data.size()) { connection.send_reply("l"); return; } - const auto remaining = target_description.size() - offset; + const auto remaining = data.size() - offset; const auto real_length = std::min(remaining, length); const auto is_end = real_length == remaining; - const auto sub_region = target_description.substr(offset, real_length); + const auto sub_region = data.substr(offset, real_length); - connection.send_reply((is_end ? "l" : "m") + sub_region); + std::string reply = is_end ? "l" : "m"; + reply.append(sub_region); + + connection.send_reply(reply); } - void process_xfer(connection_handler& connection, debugging_handler& handler, const std::string_view payload) + void handle_features(const debugging_context& c, const std::string_view payload) + { + const auto [command, args] = split_string(payload, ':'); + + if (command != "read") + { + c.connection.send_reply({}); + return; + } + + const auto [file, data] = split_string(args, ':'); + const auto target_description = c.handler.get_target_description(file); + send_xfer_data(c.connection, std::string(data), target_description); + } + + void process_xfer(const debugging_context& c, const std::string_view payload) { auto [name, args] = split_string(payload, ':'); if (name == "features") { - handle_features(connection, handler, args); + handle_features(c, args); } else { - connection.send_reply({}); + c.connection.send_reply({}); } } - void process_query(connection_handler& connection, debugging_handler& handler, const std::string_view payload) + void process_query(const debugging_context& c, const std::string_view payload) { const auto [name, args] = split_string(payload, ':'); if (name == "Supported") { - connection.send_reply("PacketSize=1024;qXfer:features:read+"); + c.connection.send_reply("PacketSize=1024;qXfer:features:read+"); } else if (name == "Attached") { - connection.send_reply("1"); + c.connection.send_reply("1"); } else if (name == "Xfer") { - process_xfer(connection, handler, args); + process_xfer(c, args); } else if (name == "Symbol") { - connection.send_reply("OK"); + c.connection.send_reply("OK"); + } + else if (name == "C") + { + const auto thread_id = c.handler.get_current_thread_id(); + c.connection.send_reply("QC" + utils::string::to_hex_number(thread_id)); + } + else if (name == "sThreadInfo") + { + c.connection.send_reply("l"); + } + else if (name == "fThreadInfo") + { + std::string reply{}; + const auto ids = c.handler.get_thread_ids(); + + for (const auto id : ids) + { + reply.push_back(reply.empty() ? 'm' : ','); + reply.append(utils::string::to_hex_number(id)); + } + + c.connection.send_reply(reply); } else { - connection.send_reply({}); + c.connection.send_reply({}); } } - void process_action(connection_handler& connection, const action a) + void process_action(const connection_handler& connection, const action a) { if (a == action::shutdown) { @@ -150,70 +191,103 @@ namespace gdb_stub return handler.delete_breakpoint(type, address, size); } - void handle_breakpoint(connection_handler& connection, debugging_handler& handler, const std::string& data, - const bool set) + void handle_breakpoint(const debugging_context& c, 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"); + const auto res = change_breakpoint(c.handler, set, translate_breakpoint_type(type), addr, kind); + c.connection.send_reply(res ? "OK" : "E01"); } - void continue_execution(connection_handler& connection, async_handler& async, debugging_handler& handler) + void signal_stop(const debugging_context& c) { - async.run(); - process_action(connection, handler.run()); - async.pause(); - connection.send_reply("S05"); + const auto id = c.handler.get_current_thread_id(); + const auto hex_id = utils::string::to_hex_number(id); + c.connection.send_reply("T05thread:" + hex_id + ";"); } - void singlestep_execution(connection_handler& connection, debugging_handler& handler) + void apply_continuation_thread(const debugging_context& c) { - process_action(connection, handler.singlestep()); - connection.send_reply("S05"); + if (c.state.continuation_thread) + { + c.handler.switch_to_thread(*c.state.continuation_thread); + c.state.continuation_thread = std::nullopt; + } } - void handle_v_packet(connection_handler& connection, async_handler& async, debugging_handler& handler, - const std::string_view data) + void resume_execution(const debugging_context& c, const bool single_step) + { + apply_continuation_thread(c); + + action a{}; + + if (single_step) + { + a = c.handler.singlestep(); + } + else + { + c.async.run(); + a = c.handler.run(); + c.async.pause(); + } + + process_action(c.connection, a); + signal_stop(c); + } + + void store_continuation_thread(const debugging_context& c, const std::string_view thread_string) + { + if (thread_string.empty()) + { + return; + } + + uint32_t thread_id{}; + rt_assert(sscanf_s(std::string(thread_string).c_str(), "%x", &thread_id) == 1); + c.state.continuation_thread = thread_id; + } + + void handle_v_packet(const debugging_context& c, const std::string_view data) { const auto [name, args] = split_string(data, ':'); if (name == "Cont?") { - connection.send_reply("vCont;s;c"); + c.connection.send_reply("vCont;s;c"); } - else if (name == "Cont;s") + else if (name == "Cont;s" || name == "Cont;c") { - singlestep_execution(connection, handler); - } - else if (name == "Cont;c") - { - continue_execution(connection, async, handler); + const auto singlestep = name[5] == 's'; + const auto [thread, _] = split_string(args, ':'); + + store_continuation_thread(c, thread); + resume_execution(c, singlestep); } else { - connection.send_reply({}); + c.connection.send_reply({}); } } - void read_registers(connection_handler& connection, debugging_handler& handler) + void read_registers(const debugging_context& c) { std::string response{}; std::vector data{}; - data.resize(handler.get_max_register_size()); + data.resize(c.handler.get_max_register_size()); - const auto registers = handler.get_register_count(); + const auto registers = c.handler.get_register_count(); for (size_t i = 0; i < registers; ++i) { - const auto size = handler.read_register(i, data.data(), data.size()); + const auto size = c.handler.read_register(i, data.data(), data.size()); if (!size) { - connection.send_reply("E01"); + c.connection.send_reply("E01"); return; } @@ -221,64 +295,62 @@ namespace gdb_stub response.append(utils::string::to_hex_string(register_data)); } - connection.send_reply(response); + c.connection.send_reply(response); } - void write_registers(connection_handler& connection, debugging_handler& handler, const std::string_view payload) + void write_registers(const debugging_context& c, 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(); + const auto registers = c.handler.get_register_count(); + const auto register_size = c.handler.get_max_register_size(); size_t offset = 0; for (size_t i = 0; i < registers; ++i) { if (offset >= data.size()) { - connection.send_reply("E01"); + c.connection.send_reply("E01"); return; } const auto max_size = std::min(register_size, data.size() - offset); - const auto size = handler.write_register(i, data.data() + offset, max_size); + const auto size = c.handler.write_register(i, data.data() + offset, max_size); offset += size; if (!size) { - connection.send_reply("E01"); + c.connection.send_reply("E01"); return; } } - connection.send_reply("OK"); + c.connection.send_reply("OK"); } - void read_single_register(connection_handler& connection, debugging_handler& handler, - const std::string& payload) + void read_single_register(const debugging_context& c, 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()); + data.resize(c.handler.get_max_register_size()); - const auto size = handler.read_register(reg, data.data(), data.size()); + const auto size = c.handler.read_register(reg, data.data(), data.size()); if (size) { const std::span register_data(data.data(), size); - connection.send_reply(utils::string::to_hex_string(register_data)); + c.connection.send_reply(utils::string::to_hex_string(register_data)); } else { - connection.send_reply("E01"); + c.connection.send_reply("E01"); } } - void write_single_register(connection_handler& connection, debugging_handler& handler, - const std::string_view payload) + void write_single_register(const debugging_context& c, const std::string_view payload) { const auto [reg, hex_data] = split_string(payload, '='); @@ -286,11 +358,11 @@ namespace gdb_stub rt_assert(sscanf_s(std::string(reg).c_str(), "%zx", ®ister_index) == 1); const auto data = utils::string::from_hex_string(hex_data); - const auto res = handler.write_register(register_index, data.data(), data.size()) > 0; - connection.send_reply(res ? "OK" : "E01"); + const auto res = c.handler.write_register(register_index, data.data(), data.size()) > 0; + c.connection.send_reply(res ? "OK" : "E01"); } - void read_memory(connection_handler& connection, debugging_handler& handler, const std::string& payload) + void read_memory(const debugging_context& c, const std::string& payload) { uint64_t address{}; size_t size{}; @@ -298,24 +370,24 @@ namespace gdb_stub if (size > 0x1000) { - connection.send_reply("E01"); + c.connection.send_reply("E01"); return; } std::vector data{}; data.resize(size); - const auto res = handler.read_memory(address, data.data(), data.size()); + const auto res = c.handler.read_memory(address, data.data(), data.size()); if (!res) { - connection.send_reply("E01"); + c.connection.send_reply("E01"); return; } - connection.send_reply(utils::string::to_hex_string(data)); + c.connection.send_reply(utils::string::to_hex_string(data)); } - void write_memory(connection_handler& connection, debugging_handler& handler, const std::string_view payload) + void write_memory(const debugging_context& c, const std::string_view payload) { const auto [info, hex_data] = split_string(payload, ':'); @@ -325,15 +397,15 @@ namespace gdb_stub if (size > 0x1000) { - connection.send_reply("E01"); + c.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"); + const auto res = c.handler.write_memory(address, data.data(), data.size()); + c.connection.send_reply(res ? "OK" : "E01"); } std::string decode_x_memory(const std::string_view payload) @@ -362,7 +434,7 @@ namespace gdb_stub return result; } - void write_x_memory(connection_handler& connection, debugging_handler& handler, const std::string_view payload) + void write_x_memory(const debugging_context& c, const std::string_view payload) { const auto [info, encoded_data] = split_string(payload, ':'); @@ -372,97 +444,127 @@ namespace gdb_stub if (size > 0x1000) { - connection.send_reply("E01"); + c.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()); + const auto res = c.handler.write_memory(address, data.data(), data.size()); if (!res) { - connection.send_reply("E01"); + c.connection.send_reply("E01"); return; } - connection.send_reply("OK"); + c.connection.send_reply("OK"); } - void handle_command(connection_handler& connection, async_handler& async, debugging_handler& handler, - const uint8_t command, const std::string_view data) + void switch_to_thread(const debugging_context& c, const std::string_view payload) { - // printf("GDB command: %c -> %.*s\n", command, static_cast(data.size()), data.data()); + if (payload.size() < 2) + { + c.connection.send_reply({}); + return; + } + + uint32_t id{}; + rt_assert(sscanf_s(std::string(payload.substr(1)).c_str(), "%x", &id) == 1); + + const auto operation = payload[0]; + if (operation == 'c') + { + c.state.continuation_thread = id; + c.connection.send_reply("OK"); + } + else if (operation == 'g') + { + const auto res = id == 0 || c.handler.switch_to_thread(id); + c.connection.send_reply(res ? "OK" : "E01"); + } + else + { + c.connection.send_reply({}); + } + } + + void handle_command(const debugging_context& c, 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': - continue_execution(connection, async, handler); + resume_execution(c, false); break; case 's': - singlestep_execution(connection, handler); + resume_execution(c, true); break; case 'q': - process_query(connection, handler, data); + process_query(c, data); break; case 'D': - connection.close(); + c.connection.close(); break; case 'z': case 'Z': - handle_breakpoint(connection, handler, std::string(data), command == 'Z'); + handle_breakpoint(c, std::string(data), command == 'Z'); break; case '?': - connection.send_reply("S05"); + signal_stop(c); break; case 'v': - handle_v_packet(connection, async, handler, data); + handle_v_packet(c, data); break; case 'g': - read_registers(connection, handler); + read_registers(c); break; case 'G': - write_registers(connection, handler, data); + write_registers(c, data); break; case 'p': - read_single_register(connection, handler, std::string(data)); + read_single_register(c, std::string(data)); break; case 'P': - write_single_register(connection, handler, data); + write_single_register(c, data); break; case 'm': - read_memory(connection, handler, std::string(data)); + read_memory(c, std::string(data)); break; case 'M': - write_memory(connection, handler, data); + write_memory(c, data); break; case 'X': - write_x_memory(connection, handler, data); + write_x_memory(c, data); + break; + + case 'H': + switch_to_thread(c, data); break; default: - connection.send_reply({}); + c.connection.send_reply({}); break; } } - void process_packet(connection_handler& connection, async_handler& async, debugging_handler& handler, - const std::string_view packet) + void process_packet(const debugging_context& c, const std::string_view packet) { - connection.send_raw_data("+"); + c.connection.send_raw_data("+"); if (packet.empty()) { @@ -470,7 +572,7 @@ namespace gdb_stub } const auto command = packet.front(); - handle_command(connection, async, handler, command, packet.substr(1)); + handle_command(c, command, packet.substr(1)); } bool is_interrupt_packet(const std::optional& data) @@ -502,8 +604,16 @@ namespace gdb_stub } }}; + debugging_state state{}; connection_handler connection{client}; + debugging_context c{ + .connection = connection, + .handler = handler, + .state = state, + .async = async, + }; + while (true) { const auto packet = connection.get_packet(); @@ -512,7 +622,7 @@ namespace gdb_stub break; } - process_packet(connection, async, handler, *packet); + process_packet(c, *packet); } return true; diff --git a/src/gdb-stub/gdb_stub.hpp b/src/gdb-stub/gdb_stub.hpp index c779aaaa..ffb22bdf 100644 --- a/src/gdb-stub/gdb_stub.hpp +++ b/src/gdb-stub/gdb_stub.hpp @@ -43,6 +43,11 @@ namespace gdb_stub virtual void on_interrupt() = 0; virtual std::string get_target_description(std::string_view file) = 0; + + virtual bool switch_to_thread(uint32_t thread_id) = 0; + + virtual uint32_t get_current_thread_id() = 0; + virtual std::vector get_thread_ids() = 0; }; bool run_gdb_stub(const network::address& bind_address, debugging_handler& handler); 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 451078a8..7b9f520e 100644 --- a/src/windows-emulator/debugging/win_x64_gdb_stub_handler.hpp +++ b/src/windows-emulator/debugging/win_x64_gdb_stub_handler.hpp @@ -40,6 +40,34 @@ class win_x64_gdb_stub_handler : public x64_gdb_stub_handler return gdb_stub::action::resume; } + uint32_t get_current_thread_id() override + { + return this->win_emu_->current_thread().id; + } + + std::vector get_thread_ids() override + { + const auto& threads = this->win_emu_->process().threads; + + std::vector ids{}; + ids.reserve(threads.size()); + + for (const auto& t : threads | std::views::values) + { + if (!t.is_terminated()) + { + ids.push_back(t.id); + } + } + + return ids; + } + + bool switch_to_thread(const uint32_t thread_id) override + { + return this->win_emu_->activate_thread(thread_id); + } + private: windows_emulator* win_emu_{}; }; diff --git a/src/windows-emulator/debugging/x64_gdb_stub_handler.hpp b/src/windows-emulator/debugging/x64_gdb_stub_handler.hpp index 70a6e039..8adf56e3 100644 --- a/src/windows-emulator/debugging/x64_gdb_stub_handler.hpp +++ b/src/windows-emulator/debugging/x64_gdb_stub_handler.hpp @@ -250,6 +250,16 @@ class x64_gdb_stub_handler : public gdb_stub::debugging_handler return entry->second; } + uint32_t get_current_thread_id() override + { + return 1; + } + + std::vector get_thread_ids() override + { + return {this->get_current_thread_id()}; + } + private: x64_emulator* emu_{}; diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index 5836ae06..ff4821ed 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -525,14 +525,39 @@ namespace } } - bool switch_to_thread(windows_emulator& win_emu, emulator_thread& thread) + emulator_thread* get_thread_by_id(process_context& process, const uint32_t id) { + for (auto& t : process.threads | std::views::values) + { + if (t.id == id) + { + return &t; + } + } + + return nullptr; + } + + bool switch_to_thread(windows_emulator& win_emu, emulator_thread& thread, const bool force = false) + { + if (thread.is_terminated()) + { + return false; + } + auto& emu = win_emu.emu(); auto& context = win_emu.process(); - if (!thread.is_thread_ready(win_emu)) + const auto is_ready = thread.is_thread_ready(win_emu); + + if (!is_ready) { - return false; + if (!force) + { + return false; + } + + win_emu.yield_thread(); } auto* active_thread = context.active_thread; @@ -553,7 +578,6 @@ namespace thread.restore(emu); thread.setup_if_necessary(emu, context); - return true; } @@ -863,6 +887,17 @@ void windows_emulator::perform_thread_switch() } } +bool windows_emulator::activate_thread(const uint32_t id) +{ + const auto thread = get_thread_by_id(this->process(), id); + if (!thread) + { + return false; + } + + return switch_to_thread(*this, *thread, true); +} + void windows_emulator::on_instruction_execution(const uint64_t address) { auto& process = this->process(); diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index acf9b7d0..36f0f9b2 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -124,6 +124,7 @@ class windows_emulator void yield_thread(); void perform_thread_switch(); + bool activate_thread(uint32_t id); bool time_is_relative() const {