2023-04-04 18:09:34 +01:00
|
|
|
// Copyright 2023 Citra Emulator Project
|
2022-12-03 21:09:13 +00:00
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
#include <fmt/ranges.h>
|
|
|
|
#include "common/string_util.h"
|
2022-12-03 01:31:31 +00:00
|
|
|
#include "core/core.h"
|
|
|
|
#include "core/gdbstub/gdbstub.h"
|
|
|
|
#include "core/gdbstub/hio.h"
|
|
|
|
|
|
|
|
namespace GDBStub {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
static VAddr current_hio_request_addr;
|
|
|
|
static PackedGdbHioRequest current_hio_request;
|
2022-12-04 20:34:06 +00:00
|
|
|
|
|
|
|
enum class Status {
|
|
|
|
NoRequest,
|
|
|
|
NotSent,
|
|
|
|
SentWaitingReply,
|
|
|
|
};
|
|
|
|
|
|
|
|
static std::atomic<Status> request_status{Status::NoRequest};
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-04-04 18:09:34 +01:00
|
|
|
static std::atomic<bool> was_halted = false;
|
|
|
|
static std::atomic<bool> was_stepping = false;
|
|
|
|
|
2022-12-03 01:31:31 +00:00
|
|
|
} // namespace
|
|
|
|
|
2023-04-04 18:09:34 +01:00
|
|
|
/**
|
|
|
|
* @return Whether the application has requested I/O, and it has not been sent.
|
|
|
|
*/
|
|
|
|
static bool HasPendingHioRequest() {
|
|
|
|
return current_hio_request_addr != 0 && request_status == Status::NotSent;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Whether the GDB stub is awaiting a reply from the client.
|
|
|
|
*/
|
2023-04-05 17:41:17 +01:00
|
|
|
static bool IsWaitingForHioReply() {
|
2023-04-04 18:09:34 +01:00
|
|
|
return current_hio_request_addr != 0 && request_status == Status::SentWaitingReply;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send a response indicating an error back to the application.
|
|
|
|
*
|
|
|
|
* @param error_code The error code to respond back to the app. This typically corresponds to errno.
|
|
|
|
*
|
|
|
|
* @param retval The return value of the syscall the app requested.
|
|
|
|
*/
|
|
|
|
static void SendErrorReply(int error_code, int retval = -1) {
|
|
|
|
auto packet = fmt::format("F{:x},{:x}", retval, error_code);
|
|
|
|
SendReply(packet.data());
|
|
|
|
}
|
|
|
|
|
2023-08-01 23:40:39 +01:00
|
|
|
void SetHioRequest(Core::System& system, const VAddr addr) {
|
2022-12-03 01:31:31 +00:00
|
|
|
if (!IsServerEnabled()) {
|
|
|
|
LOG_WARNING(Debug_GDBStub, "HIO requested but GDB stub is not running");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-05 17:41:17 +01:00
|
|
|
if (IsWaitingForHioReply()) {
|
|
|
|
LOG_WARNING(Debug_GDBStub, "HIO requested while already in progress");
|
2022-12-03 01:31:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-04 18:09:34 +01:00
|
|
|
if (HasPendingHioRequest()) {
|
|
|
|
LOG_INFO(Debug_GDBStub, "overwriting existing HIO request that was not sent yet");
|
|
|
|
}
|
|
|
|
|
2023-08-01 23:40:39 +01:00
|
|
|
auto& memory = system.Memory();
|
|
|
|
const auto process = system.Kernel().GetCurrentProcess();
|
2023-03-31 19:05:57 +01:00
|
|
|
|
|
|
|
if (!memory.IsValidVirtualAddress(*process, addr)) {
|
2022-12-03 01:31:31 +00:00
|
|
|
LOG_WARNING(Debug_GDBStub, "Invalid address for HIO request");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-08-01 23:40:39 +01:00
|
|
|
memory.ReadBlock(addr, ¤t_hio_request, sizeof(PackedGdbHioRequest));
|
2022-12-03 21:09:13 +00:00
|
|
|
|
2023-04-05 17:41:17 +01:00
|
|
|
if (current_hio_request.magic != std::array{'G', 'D', 'B', '\0'}) {
|
|
|
|
std::string_view bad_magic{
|
2023-04-05 18:27:46 +01:00
|
|
|
current_hio_request.magic.data(),
|
|
|
|
current_hio_request.magic.size(),
|
2023-04-05 17:41:17 +01:00
|
|
|
};
|
|
|
|
LOG_WARNING(Debug_GDBStub, "Invalid HIO request sent by application: bad magic '{}'",
|
|
|
|
bad_magic);
|
|
|
|
|
2022-12-03 21:48:21 +00:00
|
|
|
current_hio_request_addr = 0;
|
|
|
|
current_hio_request = {};
|
2022-12-04 20:34:06 +00:00
|
|
|
request_status = Status::NoRequest;
|
2023-03-31 19:05:57 +01:00
|
|
|
return;
|
2022-12-03 21:48:21 +00:00
|
|
|
}
|
2023-03-31 19:05:57 +01:00
|
|
|
|
|
|
|
LOG_DEBUG(Debug_GDBStub, "HIO request initiated at 0x{:X}", addr);
|
|
|
|
current_hio_request_addr = addr;
|
|
|
|
request_status = Status::NotSent;
|
|
|
|
|
2023-04-04 18:09:34 +01:00
|
|
|
was_halted = GetCpuHaltFlag();
|
|
|
|
was_stepping = GetCpuStepFlag();
|
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
// Now halt, so that no further instructions are executed until the request
|
|
|
|
// is processed by the client. We will continue after the reply comes back
|
|
|
|
Break();
|
|
|
|
SetCpuHaltFlag(true);
|
|
|
|
SetCpuStepFlag(false);
|
2023-08-01 23:40:39 +01:00
|
|
|
system.GetRunningCore().ClearInstructionCache();
|
2022-12-03 01:31:31 +00:00
|
|
|
}
|
|
|
|
|
2023-08-01 23:40:39 +01:00
|
|
|
void HandleHioReply(Core::System& system, const u8* const command_buffer,
|
|
|
|
const u32 command_length) {
|
2023-04-05 17:41:17 +01:00
|
|
|
if (!IsWaitingForHioReply()) {
|
2022-12-03 21:09:13 +00:00
|
|
|
LOG_WARNING(Debug_GDBStub, "Got HIO reply but never sent a request");
|
2023-04-04 18:09:34 +01:00
|
|
|
return;
|
2022-12-03 01:31:31 +00:00
|
|
|
}
|
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
// Skip 'F' header
|
2023-07-06 23:52:40 +01:00
|
|
|
const u8* command_pos = command_buffer + 1;
|
2022-12-03 01:31:31 +00:00
|
|
|
|
|
|
|
if (*command_pos == 0 || *command_pos == ',') {
|
2023-04-04 18:09:34 +01:00
|
|
|
LOG_WARNING(Debug_GDBStub, "bad HIO packet format position 0: {}", *command_pos);
|
|
|
|
SendErrorReply(EILSEQ);
|
|
|
|
return;
|
2022-12-03 21:09:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set the sign of the retval
|
|
|
|
if (*command_pos == '-') {
|
2022-12-03 01:31:31 +00:00
|
|
|
command_pos++;
|
|
|
|
current_hio_request.retval = -1;
|
|
|
|
} else {
|
2023-03-31 19:05:57 +01:00
|
|
|
if (*command_pos == '+') {
|
|
|
|
command_pos++;
|
|
|
|
}
|
|
|
|
|
2022-12-03 01:31:31 +00:00
|
|
|
current_hio_request.retval = 1;
|
|
|
|
}
|
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
const std::string command_str{command_pos, command_buffer + command_length};
|
2023-04-27 05:38:28 +01:00
|
|
|
const auto command_parts = Common::SplitString(command_str, ',');
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
if (command_parts.empty() || command_parts.size() > 3) {
|
2023-04-05 17:41:17 +01:00
|
|
|
LOG_WARNING(Debug_GDBStub, "Unexpected reply packet size: {}", command_parts);
|
2023-04-04 18:09:34 +01:00
|
|
|
SendErrorReply(EILSEQ);
|
|
|
|
return;
|
2023-03-31 19:05:57 +01:00
|
|
|
}
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-04-04 18:21:25 +01:00
|
|
|
u64 unsigned_retval =
|
|
|
|
HexToInt(reinterpret_cast<const u8*>(command_parts[0].data()), command_parts[0].size());
|
2023-03-31 19:05:57 +01:00
|
|
|
current_hio_request.retval *= unsigned_retval;
|
2022-12-03 21:09:13 +00:00
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
if (command_parts.size() > 1) {
|
|
|
|
// Technically the errno could be signed but in practice this doesn't happen
|
|
|
|
current_hio_request.gdb_errno =
|
2023-04-04 18:21:25 +01:00
|
|
|
HexToInt(reinterpret_cast<const u8*>(command_parts[1].data()), command_parts[1].size());
|
2023-03-31 19:05:57 +01:00
|
|
|
} else {
|
|
|
|
current_hio_request.gdb_errno = 0;
|
|
|
|
}
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
current_hio_request.ctrl_c = false;
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
if (command_parts.size() > 2 && !command_parts[2].empty()) {
|
|
|
|
if (command_parts[2][0] != 'C') {
|
2023-04-04 18:09:34 +01:00
|
|
|
LOG_WARNING(Debug_GDBStub, "expected ctrl-c flag got '{}'", command_parts[2][0]);
|
|
|
|
SendErrorReply(EILSEQ);
|
|
|
|
return;
|
2022-12-03 01:31:31 +00:00
|
|
|
}
|
2023-03-31 19:05:57 +01:00
|
|
|
|
|
|
|
// for now we just ignore any trailing ";..." attachments
|
|
|
|
current_hio_request.ctrl_c = true;
|
2022-12-03 01:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::fill(std::begin(current_hio_request.param_format),
|
|
|
|
std::end(current_hio_request.param_format), 0);
|
|
|
|
|
2023-03-31 19:05:57 +01:00
|
|
|
LOG_TRACE(Debug_GDBStub, "HIO reply: {{retval = {}, errno = {}, ctrl_c = {}}}",
|
2022-12-03 21:09:13 +00:00
|
|
|
current_hio_request.retval, current_hio_request.gdb_errno,
|
|
|
|
current_hio_request.ctrl_c);
|
|
|
|
|
2023-08-01 23:40:39 +01:00
|
|
|
const auto process = system.Kernel().GetCurrentProcess();
|
|
|
|
auto& memory = system.Memory();
|
2023-03-31 19:05:57 +01:00
|
|
|
|
2022-12-03 21:09:13 +00:00
|
|
|
// should have been checked when we first initialized the request,
|
|
|
|
// but just double check again before we write to memory
|
2023-03-31 19:05:57 +01:00
|
|
|
if (!memory.IsValidVirtualAddress(*process, current_hio_request_addr)) {
|
|
|
|
LOG_WARNING(Debug_GDBStub, "Invalid address {:#X} to write HIO reply",
|
2022-12-03 21:09:13 +00:00
|
|
|
current_hio_request_addr);
|
2023-04-04 18:09:34 +01:00
|
|
|
return;
|
2022-12-03 21:09:13 +00:00
|
|
|
}
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-08-01 23:40:39 +01:00
|
|
|
memory.WriteBlock(current_hio_request_addr, ¤t_hio_request, sizeof(PackedGdbHioRequest));
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2022-12-04 20:34:06 +00:00
|
|
|
current_hio_request = {};
|
2022-12-03 01:31:31 +00:00
|
|
|
current_hio_request_addr = 0;
|
2022-12-04 20:34:06 +00:00
|
|
|
request_status = Status::NoRequest;
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-04-04 18:09:34 +01:00
|
|
|
// Restore state from before the request came in
|
|
|
|
SetCpuStepFlag(was_stepping);
|
|
|
|
SetCpuHaltFlag(was_halted);
|
2023-08-01 23:40:39 +01:00
|
|
|
system.GetRunningCore().ClearInstructionCache();
|
2022-12-03 01:31:31 +00:00
|
|
|
}
|
|
|
|
|
2023-04-04 18:09:34 +01:00
|
|
|
bool HandlePendingHioRequestPacket() {
|
|
|
|
if (!HasPendingHioRequest()) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-12-04 20:34:06 +00:00
|
|
|
|
2023-04-05 17:41:17 +01:00
|
|
|
if (IsWaitingForHioReply()) {
|
2023-04-04 18:09:34 +01:00
|
|
|
// We already sent it, continue waiting for a reply
|
|
|
|
return true;
|
|
|
|
}
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-04-05 17:41:17 +01:00
|
|
|
// We need a null-terminated string from char* instead of using
|
|
|
|
// the full length of the array (like {.begin(), .end()} constructor would)
|
|
|
|
std::string_view function_name{current_hio_request.function_name.data()};
|
|
|
|
|
|
|
|
std::string packet = fmt::format("F{}", function_name);
|
2022-12-03 01:31:31 +00:00
|
|
|
|
2023-04-05 17:41:17 +01:00
|
|
|
u32 str_length_idx = 0;
|
2022-12-03 01:31:31 +00:00
|
|
|
|
|
|
|
for (u32 i = 0; i < 8 && current_hio_request.param_format[i] != 0; i++) {
|
2023-03-31 19:05:57 +01:00
|
|
|
u64 param = current_hio_request.parameters[i];
|
|
|
|
|
2023-04-05 17:41:17 +01:00
|
|
|
// TODO: should we use the IntToGdbHex funcs instead of fmt::format_to ?
|
2022-12-03 01:31:31 +00:00
|
|
|
switch (current_hio_request.param_format[i]) {
|
|
|
|
case 'i':
|
|
|
|
case 'I':
|
|
|
|
case 'p':
|
2023-04-05 17:41:17 +01:00
|
|
|
// For pointers and 32-bit ints, truncate down to size before sending
|
2023-03-31 19:05:57 +01:00
|
|
|
param = static_cast<u32>(param);
|
2023-04-05 17:41:17 +01:00
|
|
|
[[fallthrough]];
|
2023-03-31 19:05:57 +01:00
|
|
|
|
2022-12-03 01:31:31 +00:00
|
|
|
case 'l':
|
|
|
|
case 'L':
|
2023-03-31 19:05:57 +01:00
|
|
|
fmt::format_to(std::back_inserter(packet), ",{:x}", param);
|
2022-12-03 01:31:31 +00:00
|
|
|
break;
|
2023-03-31 19:05:57 +01:00
|
|
|
|
2022-12-03 01:31:31 +00:00
|
|
|
case 's':
|
2023-03-31 19:05:57 +01:00
|
|
|
// strings are written as {pointer}/{length}
|
|
|
|
fmt::format_to(std::back_inserter(packet), ",{:x}/{:x}",
|
2023-04-04 18:21:25 +01:00
|
|
|
static_cast<u32>(current_hio_request.parameters[i]),
|
2023-04-05 17:41:17 +01:00
|
|
|
current_hio_request.string_lengths[str_length_idx++]);
|
2022-12-03 01:31:31 +00:00
|
|
|
break;
|
2023-03-31 19:05:57 +01:00
|
|
|
|
2022-12-03 01:31:31 +00:00
|
|
|
default:
|
2023-04-04 18:09:34 +01:00
|
|
|
LOG_WARNING(Debug_GDBStub, "unexpected hio request param format '{}'",
|
|
|
|
current_hio_request.param_format[i]);
|
|
|
|
SendErrorReply(EILSEQ);
|
|
|
|
return false;
|
2022-12-03 01:31:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-05 17:41:17 +01:00
|
|
|
LOG_TRACE(Debug_GDBStub, "HIO request packet: '{}'", packet);
|
2022-12-04 20:34:06 +00:00
|
|
|
|
2023-04-04 18:09:34 +01:00
|
|
|
SendReply(packet.data());
|
2022-12-04 20:34:06 +00:00
|
|
|
request_status = Status::SentWaitingReply;
|
2023-04-04 18:09:34 +01:00
|
|
|
return true;
|
2022-12-03 01:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace GDBStub
|