Merge pull request #6395 from ian-h-chamberlain/feature/gdbstub-hio
Initial port of luma3ds' gdb_hio to Citra
This commit is contained in:
commit
2a2ee8bc96
|
@ -115,6 +115,8 @@ add_library(core STATIC
|
||||||
frontend/mic.h
|
frontend/mic.h
|
||||||
gdbstub/gdbstub.cpp
|
gdbstub/gdbstub.cpp
|
||||||
gdbstub/gdbstub.h
|
gdbstub/gdbstub.h
|
||||||
|
gdbstub/hio.cpp
|
||||||
|
gdbstub/hio.h
|
||||||
hle/applets/applet.cpp
|
hle/applets/applet.cpp
|
||||||
hle/applets/applet.h
|
hle/applets/applet.h
|
||||||
hle/applets/erreula.cpp
|
hle/applets/erreula.cpp
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#include "core/gdbstub/hio.h"
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
@ -261,13 +262,7 @@ static u8 NibbleToHex(u8 n) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
u32 HexToInt(const u8* src, std::size_t len) {
|
||||||
* Converts input hex string characters into an array of equivalent of u8 bytes.
|
|
||||||
*
|
|
||||||
* @param src Pointer to array of output hex string characters.
|
|
||||||
* @param len Length of src array.
|
|
||||||
*/
|
|
||||||
static u32 HexToInt(const u8* src, std::size_t len) {
|
|
||||||
u32 output = 0;
|
u32 output = 0;
|
||||||
while (len-- > 0) {
|
while (len-- > 0) {
|
||||||
output = (output << 4) | HexCharToValue(src[0]);
|
output = (output << 4) | HexCharToValue(src[0]);
|
||||||
|
@ -492,12 +487,7 @@ static void SendPacket(const char packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void SendReply(const char* reply) {
|
||||||
* Send reply to gdb client.
|
|
||||||
*
|
|
||||||
* @param reply Reply to be sent to client.
|
|
||||||
*/
|
|
||||||
static void SendReply(const char* reply) {
|
|
||||||
if (!IsConnected()) {
|
if (!IsConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -653,7 +643,7 @@ static void ReadCommand() {
|
||||||
memset(command_buffer, 0, sizeof(command_buffer));
|
memset(command_buffer, 0, sizeof(command_buffer));
|
||||||
|
|
||||||
u8 c = ReadByte();
|
u8 c = ReadByte();
|
||||||
if (c == '+') {
|
if (c == GDB_STUB_ACK) {
|
||||||
// ignore ack
|
// ignore ack
|
||||||
return;
|
return;
|
||||||
} else if (c == 0x03) {
|
} else if (c == 0x03) {
|
||||||
|
@ -843,7 +833,7 @@ static void ReadMemory() {
|
||||||
u32 len =
|
u32 len =
|
||||||
HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset));
|
HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset));
|
||||||
|
|
||||||
LOG_DEBUG(Debug_GDBStub, "gdb: addr: {:08x} len: {:08x}\n", addr, len);
|
LOG_DEBUG(Debug_GDBStub, "ReadMemory addr: {:08x} len: {:08x}", addr, len);
|
||||||
|
|
||||||
if (len * 2 > sizeof(reply)) {
|
if (len * 2 > sizeof(reply)) {
|
||||||
SendReply("E01");
|
SendReply("E01");
|
||||||
|
@ -860,7 +850,11 @@ static void ReadMemory() {
|
||||||
|
|
||||||
MemToGdbHex(reply, data.data(), len);
|
MemToGdbHex(reply, data.data(), len);
|
||||||
reply[len * 2] = '\0';
|
reply[len * 2] = '\0';
|
||||||
SendReply(reinterpret_cast<char*>(reply));
|
|
||||||
|
auto reply_str = reinterpret_cast<char*>(reply);
|
||||||
|
|
||||||
|
LOG_DEBUG(Debug_GDBStub, "ReadMemory result: {}", reply_str);
|
||||||
|
SendReply(reply_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modify location in memory with data received from the gdb client.
|
/// Modify location in memory with data received from the gdb client.
|
||||||
|
@ -1050,6 +1044,11 @@ void HandlePacket() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HandlePendingHioRequestPacket()) {
|
||||||
|
// Don't do anything else while we wait for the client to respond
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsDataAvailable()) {
|
if (!IsDataAvailable()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1059,7 +1058,7 @@ void HandlePacket() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG(Debug_GDBStub, "Packet: {}", command_buffer[0]);
|
LOG_DEBUG(Debug_GDBStub, "Packet: {0:d} ('{0:c}')", command_buffer[0]);
|
||||||
|
|
||||||
switch (command_buffer[0]) {
|
switch (command_buffer[0]) {
|
||||||
case 'q':
|
case 'q':
|
||||||
|
@ -1072,9 +1071,14 @@ void HandlePacket() {
|
||||||
SendSignal(current_thread, latest_signal);
|
SendSignal(current_thread, latest_signal);
|
||||||
break;
|
break;
|
||||||
case 'k':
|
case 'k':
|
||||||
Shutdown();
|
|
||||||
LOG_INFO(Debug_GDBStub, "killed by gdb");
|
LOG_INFO(Debug_GDBStub, "killed by gdb");
|
||||||
|
ToggleServer(false);
|
||||||
|
// Continue execution so we don't hang forever after shutting down the server
|
||||||
|
Continue();
|
||||||
return;
|
return;
|
||||||
|
case 'F':
|
||||||
|
HandleHioReply(command_buffer, command_length);
|
||||||
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
ReadRegisters();
|
ReadRegisters();
|
||||||
break;
|
break;
|
||||||
|
@ -1251,6 +1255,10 @@ bool GetCpuHaltFlag() {
|
||||||
return halt_loop;
|
return halt_loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetCpuHaltFlag(bool halt) {
|
||||||
|
halt_loop = halt;
|
||||||
|
}
|
||||||
|
|
||||||
bool GetCpuStepFlag() {
|
bool GetCpuStepFlag() {
|
||||||
return step_loop;
|
return step_loop;
|
||||||
}
|
}
|
||||||
|
@ -1265,6 +1273,7 @@ void SendTrap(Kernel::Thread* thread, int trap) {
|
||||||
}
|
}
|
||||||
|
|
||||||
current_thread = thread;
|
current_thread = thread;
|
||||||
|
|
||||||
SendSignal(thread, trap);
|
SendSignal(thread, trap);
|
||||||
|
|
||||||
halt_loop = true;
|
halt_loop = true;
|
||||||
|
|
|
@ -90,6 +90,13 @@ bool CheckBreakpoint(VAddr addr, GDBStub::BreakpointType type);
|
||||||
// If set to true, the CPU will halt at the beginning of the next CPU loop.
|
// If set to true, the CPU will halt at the beginning of the next CPU loop.
|
||||||
bool GetCpuHaltFlag();
|
bool GetCpuHaltFlag();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to true, the CPU will halt at the beginning of the next CPU loop.
|
||||||
|
*
|
||||||
|
* @param halt whether to halt on the next loop
|
||||||
|
*/
|
||||||
|
void SetCpuHaltFlag(bool halt);
|
||||||
|
|
||||||
// If set to true and the CPU is halted, the CPU will step one instruction.
|
// If set to true and the CPU is halted, the CPU will step one instruction.
|
||||||
bool GetCpuStepFlag();
|
bool GetCpuStepFlag();
|
||||||
|
|
||||||
|
@ -107,4 +114,19 @@ void SetCpuStepFlag(bool is_step);
|
||||||
* @param trap Trap no.
|
* @param trap Trap no.
|
||||||
*/
|
*/
|
||||||
void SendTrap(Kernel::Thread* thread, int trap);
|
void SendTrap(Kernel::Thread* thread, int trap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send reply to gdb client.
|
||||||
|
*
|
||||||
|
* @param reply Reply to be sent to client.
|
||||||
|
*/
|
||||||
|
void SendReply(const char* reply);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts input hex string characters into an array of equivalent of u8 bytes.
|
||||||
|
*
|
||||||
|
* @param src Pointer to array of output hex string characters.
|
||||||
|
* @param len Length of src array.
|
||||||
|
*/
|
||||||
|
u32 HexToInt(const u8* src, std::size_t len);
|
||||||
} // namespace GDBStub
|
} // namespace GDBStub
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <fmt/ranges.h>
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#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;
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
NoRequest,
|
||||||
|
NotSent,
|
||||||
|
SentWaitingReply,
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::atomic<Status> request_status{Status::NoRequest};
|
||||||
|
|
||||||
|
static std::atomic<bool> was_halted = false;
|
||||||
|
static std::atomic<bool> was_stepping = false;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
static bool IsWaitingForHioReply() {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetHioRequest(const VAddr addr) {
|
||||||
|
if (!IsServerEnabled()) {
|
||||||
|
LOG_WARNING(Debug_GDBStub, "HIO requested but GDB stub is not running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsWaitingForHioReply()) {
|
||||||
|
LOG_WARNING(Debug_GDBStub, "HIO requested while already in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasPendingHioRequest()) {
|
||||||
|
LOG_INFO(Debug_GDBStub, "overwriting existing HIO request that was not sent yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& memory = Core::System::GetInstance().Memory();
|
||||||
|
const auto process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||||
|
|
||||||
|
if (!memory.IsValidVirtualAddress(*process, addr)) {
|
||||||
|
LOG_WARNING(Debug_GDBStub, "Invalid address for HIO request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory.ReadBlock(*process, addr, ¤t_hio_request, sizeof(PackedGdbHioRequest));
|
||||||
|
|
||||||
|
if (current_hio_request.magic != std::array{'G', 'D', 'B', '\0'}) {
|
||||||
|
std::string_view bad_magic{
|
||||||
|
current_hio_request.magic.data(),
|
||||||
|
current_hio_request.magic.size(),
|
||||||
|
};
|
||||||
|
LOG_WARNING(Debug_GDBStub, "Invalid HIO request sent by application: bad magic '{}'",
|
||||||
|
bad_magic);
|
||||||
|
|
||||||
|
current_hio_request_addr = 0;
|
||||||
|
current_hio_request = {};
|
||||||
|
request_status = Status::NoRequest;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(Debug_GDBStub, "HIO request initiated at 0x{:X}", addr);
|
||||||
|
current_hio_request_addr = addr;
|
||||||
|
request_status = Status::NotSent;
|
||||||
|
|
||||||
|
was_halted = GetCpuHaltFlag();
|
||||||
|
was_stepping = GetCpuStepFlag();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
Core::GetRunningCore().ClearInstructionCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleHioReply(const u8* const command_buffer, const u32 command_length) {
|
||||||
|
if (!IsWaitingForHioReply()) {
|
||||||
|
LOG_WARNING(Debug_GDBStub, "Got HIO reply but never sent a request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip 'F' header
|
||||||
|
auto* command_pos = command_buffer + 1;
|
||||||
|
|
||||||
|
if (*command_pos == 0 || *command_pos == ',') {
|
||||||
|
LOG_WARNING(Debug_GDBStub, "bad HIO packet format position 0: {}", *command_pos);
|
||||||
|
SendErrorReply(EILSEQ);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the sign of the retval
|
||||||
|
if (*command_pos == '-') {
|
||||||
|
command_pos++;
|
||||||
|
current_hio_request.retval = -1;
|
||||||
|
} else {
|
||||||
|
if (*command_pos == '+') {
|
||||||
|
command_pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_hio_request.retval = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string command_str{command_pos, command_buffer + command_length};
|
||||||
|
std::vector<std::string> command_parts;
|
||||||
|
Common::SplitString(command_str, ',', command_parts);
|
||||||
|
|
||||||
|
if (command_parts.empty() || command_parts.size() > 3) {
|
||||||
|
LOG_WARNING(Debug_GDBStub, "Unexpected reply packet size: {}", command_parts);
|
||||||
|
SendErrorReply(EILSEQ);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 unsigned_retval =
|
||||||
|
HexToInt(reinterpret_cast<const u8*>(command_parts[0].data()), command_parts[0].size());
|
||||||
|
current_hio_request.retval *= unsigned_retval;
|
||||||
|
|
||||||
|
if (command_parts.size() > 1) {
|
||||||
|
// Technically the errno could be signed but in practice this doesn't happen
|
||||||
|
current_hio_request.gdb_errno =
|
||||||
|
HexToInt(reinterpret_cast<const u8*>(command_parts[1].data()), command_parts[1].size());
|
||||||
|
} else {
|
||||||
|
current_hio_request.gdb_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_hio_request.ctrl_c = false;
|
||||||
|
|
||||||
|
if (command_parts.size() > 2 && !command_parts[2].empty()) {
|
||||||
|
if (command_parts[2][0] != 'C') {
|
||||||
|
LOG_WARNING(Debug_GDBStub, "expected ctrl-c flag got '{}'", command_parts[2][0]);
|
||||||
|
SendErrorReply(EILSEQ);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for now we just ignore any trailing ";..." attachments
|
||||||
|
current_hio_request.ctrl_c = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fill(std::begin(current_hio_request.param_format),
|
||||||
|
std::end(current_hio_request.param_format), 0);
|
||||||
|
|
||||||
|
LOG_TRACE(Debug_GDBStub, "HIO reply: {{retval = {}, errno = {}, ctrl_c = {}}}",
|
||||||
|
current_hio_request.retval, current_hio_request.gdb_errno,
|
||||||
|
current_hio_request.ctrl_c);
|
||||||
|
|
||||||
|
const auto process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||||
|
auto& memory = Core::System::GetInstance().Memory();
|
||||||
|
|
||||||
|
// should have been checked when we first initialized the request,
|
||||||
|
// but just double check again before we write to memory
|
||||||
|
if (!memory.IsValidVirtualAddress(*process, current_hio_request_addr)) {
|
||||||
|
LOG_WARNING(Debug_GDBStub, "Invalid address {:#X} to write HIO reply",
|
||||||
|
current_hio_request_addr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory.WriteBlock(*process, current_hio_request_addr, ¤t_hio_request,
|
||||||
|
sizeof(PackedGdbHioRequest));
|
||||||
|
|
||||||
|
current_hio_request = {};
|
||||||
|
current_hio_request_addr = 0;
|
||||||
|
request_status = Status::NoRequest;
|
||||||
|
|
||||||
|
// Restore state from before the request came in
|
||||||
|
SetCpuStepFlag(was_stepping);
|
||||||
|
SetCpuHaltFlag(was_halted);
|
||||||
|
Core::GetRunningCore().ClearInstructionCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandlePendingHioRequestPacket() {
|
||||||
|
if (!HasPendingHioRequest()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsWaitingForHioReply()) {
|
||||||
|
// We already sent it, continue waiting for a reply
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
u32 str_length_idx = 0;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < 8 && current_hio_request.param_format[i] != 0; i++) {
|
||||||
|
u64 param = current_hio_request.parameters[i];
|
||||||
|
|
||||||
|
// TODO: should we use the IntToGdbHex funcs instead of fmt::format_to ?
|
||||||
|
switch (current_hio_request.param_format[i]) {
|
||||||
|
case 'i':
|
||||||
|
case 'I':
|
||||||
|
case 'p':
|
||||||
|
// For pointers and 32-bit ints, truncate down to size before sending
|
||||||
|
param = static_cast<u32>(param);
|
||||||
|
[[fallthrough]];
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
case 'L':
|
||||||
|
fmt::format_to(std::back_inserter(packet), ",{:x}", param);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
// strings are written as {pointer}/{length}
|
||||||
|
fmt::format_to(std::back_inserter(packet), ",{:x}/{:x}",
|
||||||
|
static_cast<u32>(current_hio_request.parameters[i]),
|
||||||
|
current_hio_request.string_lengths[str_length_idx++]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG_WARNING(Debug_GDBStub, "unexpected hio request param format '{}'",
|
||||||
|
current_hio_request.param_format[i]);
|
||||||
|
SendErrorReply(EILSEQ);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_TRACE(Debug_GDBStub, "HIO request packet: '{}'", packet);
|
||||||
|
|
||||||
|
SendReply(packet.data());
|
||||||
|
request_status = Status::SentWaitingReply;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GDBStub
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace GDBStub {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request from a debugged application to perform some I/O with the GDB client.
|
||||||
|
* This structure is also used to encode the reply back to the application.
|
||||||
|
*
|
||||||
|
* Based on the Rosalina + libctru implementations:
|
||||||
|
* https://github.com/LumaTeam/Luma3DS/blob/master/sysmodules/rosalina/include/gdb.h#L46C27-L62
|
||||||
|
* https://github.com/devkitPro/libctru/blob/master/libctru/source/gdbhio.c#L71-L87
|
||||||
|
*/
|
||||||
|
struct PackedGdbHioRequest {
|
||||||
|
std::array<char, 4> magic; // "GDB\0"
|
||||||
|
u32 version;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static inline constexpr std::size_t MAX_FUNCNAME_LEN = 16;
|
||||||
|
static inline constexpr std::size_t PARAM_COUNT = 8;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Request. Char arrays have +1 entry for null terminator
|
||||||
|
std::array<char, MAX_FUNCNAME_LEN + 1> function_name;
|
||||||
|
std::array<char, PARAM_COUNT + 1> param_format;
|
||||||
|
|
||||||
|
std::array<u64, PARAM_COUNT> parameters;
|
||||||
|
std::array<u32, PARAM_COUNT> string_lengths;
|
||||||
|
|
||||||
|
// Return
|
||||||
|
s64 retval;
|
||||||
|
s32 gdb_errno;
|
||||||
|
bool ctrl_c;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(PackedGdbHioRequest) == 152,
|
||||||
|
"HIO request size must match libctru implementation");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current HIO request to the given address. This is how the debugged
|
||||||
|
* app indicates to the gdbstub that it wishes to perform a request.
|
||||||
|
*
|
||||||
|
* @param address The memory address of the \ref PackedGdbHioRequest.
|
||||||
|
*/
|
||||||
|
void SetHioRequest(const VAddr address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is a pending HIO request, send it to the client.
|
||||||
|
*
|
||||||
|
* @returns whethere any request was sent to the client.
|
||||||
|
*/
|
||||||
|
bool HandlePendingHioRequestPacket();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an HIO reply from the client.
|
||||||
|
*/
|
||||||
|
void HandleHioReply(const u8* const command_buffer, const u32 command_length);
|
||||||
|
|
||||||
|
} // namespace GDBStub
|
|
@ -13,6 +13,7 @@
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#include "core/gdbstub/hio.h"
|
||||||
#include "core/hle/kernel/address_arbiter.h"
|
#include "core/hle/kernel/address_arbiter.h"
|
||||||
#include "core/hle/kernel/client_port.h"
|
#include "core/hle/kernel/client_port.h"
|
||||||
#include "core/hle/kernel/client_session.h"
|
#include "core/hle/kernel/client_session.h"
|
||||||
|
@ -1140,9 +1141,21 @@ void SVC::Break(u8 break_reason) {
|
||||||
system.SetStatus(Core::System::ResultStatus::ErrorUnknown);
|
system.SetStatus(Core::System::ResultStatus::ErrorUnknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
|
/// Used to output a message on a debug hardware unit, or for the GDB file I/O
|
||||||
|
/// (HIO) protocol - does nothing on a retail unit.
|
||||||
void SVC::OutputDebugString(VAddr address, s32 len) {
|
void SVC::OutputDebugString(VAddr address, s32 len) {
|
||||||
if (len <= 0) {
|
if (!memory.IsValidVirtualAddress(*kernel.GetCurrentProcess(), address)) {
|
||||||
|
LOG_WARNING(Kernel_SVC, "OutputDebugString called with invalid address {:X}", address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
GDBStub::SetHioRequest(address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < 0) {
|
||||||
|
LOG_WARNING(Kernel_SVC, "OutputDebugString called with invalid length {}", len);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in New Issue