hio.cpp
1 // Copyright 2023 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include <fmt/ranges.h> 6 #include "common/string_util.h" 7 #include "core/core.h" 8 #include "core/gdbstub/gdbstub.h" 9 #include "core/gdbstub/hio.h" 10 11 namespace GDBStub { 12 13 namespace { 14 15 static VAddr current_hio_request_addr; 16 static PackedGdbHioRequest current_hio_request; 17 18 enum class Status { 19 NoRequest, 20 NotSent, 21 SentWaitingReply, 22 }; 23 24 static std::atomic<Status> request_status{Status::NoRequest}; 25 26 static std::atomic<bool> was_halted = false; 27 static std::atomic<bool> was_stepping = false; 28 29 } // namespace 30 31 /** 32 * @return Whether the application has requested I/O, and it has not been sent. 33 */ 34 static bool HasPendingHioRequest() { 35 return current_hio_request_addr != 0 && request_status == Status::NotSent; 36 } 37 38 /** 39 * @return Whether the GDB stub is awaiting a reply from the client. 40 */ 41 static bool IsWaitingForHioReply() { 42 return current_hio_request_addr != 0 && request_status == Status::SentWaitingReply; 43 } 44 45 /** 46 * Send a response indicating an error back to the application. 47 * 48 * @param error_code The error code to respond back to the app. This typically corresponds to errno. 49 * 50 * @param retval The return value of the syscall the app requested. 51 */ 52 static void SendErrorReply(int error_code, int retval = -1) { 53 auto packet = fmt::format("F{:x},{:x}", retval, error_code); 54 SendReply(packet.data()); 55 } 56 57 void SetHioRequest(Core::System& system, const VAddr addr) { 58 if (!IsServerEnabled()) { 59 LOG_WARNING(Debug_GDBStub, "HIO requested but GDB stub is not running"); 60 return; 61 } 62 63 if (IsWaitingForHioReply()) { 64 LOG_WARNING(Debug_GDBStub, "HIO requested while already in progress"); 65 return; 66 } 67 68 if (HasPendingHioRequest()) { 69 LOG_INFO(Debug_GDBStub, "overwriting existing HIO request that was not sent yet"); 70 } 71 72 auto& memory = system.Memory(); 73 const auto process = system.Kernel().GetCurrentProcess(); 74 75 if (!memory.IsValidVirtualAddress(*process, addr)) { 76 LOG_WARNING(Debug_GDBStub, "Invalid address for HIO request"); 77 return; 78 } 79 80 memory.ReadBlock(addr, ¤t_hio_request, sizeof(PackedGdbHioRequest)); 81 82 if (current_hio_request.magic != std::array{'G', 'D', 'B', '\0'}) { 83 std::string_view bad_magic{ 84 current_hio_request.magic.data(), 85 current_hio_request.magic.size(), 86 }; 87 LOG_WARNING(Debug_GDBStub, "Invalid HIO request sent by application: bad magic '{}'", 88 bad_magic); 89 90 current_hio_request_addr = 0; 91 current_hio_request = {}; 92 request_status = Status::NoRequest; 93 return; 94 } 95 96 LOG_DEBUG(Debug_GDBStub, "HIO request initiated at 0x{:X}", addr); 97 current_hio_request_addr = addr; 98 request_status = Status::NotSent; 99 100 was_halted = GetCpuHaltFlag(); 101 was_stepping = GetCpuStepFlag(); 102 103 // Now halt, so that no further instructions are executed until the request 104 // is processed by the client. We will continue after the reply comes back 105 Break(); 106 SetCpuHaltFlag(true); 107 SetCpuStepFlag(false); 108 system.GetRunningCore().ClearInstructionCache(); 109 } 110 111 void HandleHioReply(Core::System& system, const u8* const command_buffer, 112 const u32 command_length) { 113 if (!IsWaitingForHioReply()) { 114 LOG_WARNING(Debug_GDBStub, "Got HIO reply but never sent a request"); 115 return; 116 } 117 118 // Skip 'F' header 119 const u8* command_pos = command_buffer + 1; 120 121 if (*command_pos == 0 || *command_pos == ',') { 122 LOG_WARNING(Debug_GDBStub, "bad HIO packet format position 0: {}", *command_pos); 123 SendErrorReply(EILSEQ); 124 return; 125 } 126 127 // Set the sign of the retval 128 if (*command_pos == '-') { 129 command_pos++; 130 current_hio_request.retval = -1; 131 } else { 132 if (*command_pos == '+') { 133 command_pos++; 134 } 135 136 current_hio_request.retval = 1; 137 } 138 139 const std::string command_str{command_pos, command_buffer + command_length}; 140 const auto command_parts = Common::SplitString(command_str, ','); 141 142 if (command_parts.empty() || command_parts.size() > 3) { 143 LOG_WARNING(Debug_GDBStub, "Unexpected reply packet size: {}", command_parts); 144 SendErrorReply(EILSEQ); 145 return; 146 } 147 148 u64 unsigned_retval = 149 HexToInt(reinterpret_cast<const u8*>(command_parts[0].data()), command_parts[0].size()); 150 current_hio_request.retval *= unsigned_retval; 151 152 if (command_parts.size() > 1) { 153 // Technically the errno could be signed but in practice this doesn't happen 154 current_hio_request.gdb_errno = 155 HexToInt(reinterpret_cast<const u8*>(command_parts[1].data()), command_parts[1].size()); 156 } else { 157 current_hio_request.gdb_errno = 0; 158 } 159 160 current_hio_request.ctrl_c = false; 161 162 if (command_parts.size() > 2 && !command_parts[2].empty()) { 163 if (command_parts[2][0] != 'C') { 164 LOG_WARNING(Debug_GDBStub, "expected ctrl-c flag got '{}'", command_parts[2][0]); 165 SendErrorReply(EILSEQ); 166 return; 167 } 168 169 // for now we just ignore any trailing ";..." attachments 170 current_hio_request.ctrl_c = true; 171 } 172 173 std::fill(std::begin(current_hio_request.param_format), 174 std::end(current_hio_request.param_format), 0); 175 176 LOG_TRACE(Debug_GDBStub, "HIO reply: {{retval = {}, errno = {}, ctrl_c = {}}}", 177 current_hio_request.retval, current_hio_request.gdb_errno, 178 current_hio_request.ctrl_c); 179 180 const auto process = system.Kernel().GetCurrentProcess(); 181 auto& memory = system.Memory(); 182 183 // should have been checked when we first initialized the request, 184 // but just double check again before we write to memory 185 if (!memory.IsValidVirtualAddress(*process, current_hio_request_addr)) { 186 LOG_WARNING(Debug_GDBStub, "Invalid address {:#X} to write HIO reply", 187 current_hio_request_addr); 188 return; 189 } 190 191 memory.WriteBlock(current_hio_request_addr, ¤t_hio_request, sizeof(PackedGdbHioRequest)); 192 193 current_hio_request = {}; 194 current_hio_request_addr = 0; 195 request_status = Status::NoRequest; 196 197 // Restore state from before the request came in 198 SetCpuStepFlag(was_stepping); 199 SetCpuHaltFlag(was_halted); 200 system.GetRunningCore().ClearInstructionCache(); 201 } 202 203 bool HandlePendingHioRequestPacket() { 204 if (!HasPendingHioRequest()) { 205 return false; 206 } 207 208 if (IsWaitingForHioReply()) { 209 // We already sent it, continue waiting for a reply 210 return true; 211 } 212 213 // We need a null-terminated string from char* instead of using 214 // the full length of the array (like {.begin(), .end()} constructor would) 215 std::string_view function_name{current_hio_request.function_name.data()}; 216 217 std::string packet = fmt::format("F{}", function_name); 218 219 u32 str_length_idx = 0; 220 221 for (u32 i = 0; i < 8 && current_hio_request.param_format[i] != 0; i++) { 222 u64 param = current_hio_request.parameters[i]; 223 224 // TODO: should we use the IntToGdbHex funcs instead of fmt::format_to ? 225 switch (current_hio_request.param_format[i]) { 226 case 'i': 227 case 'I': 228 case 'p': 229 // For pointers and 32-bit ints, truncate down to size before sending 230 param = static_cast<u32>(param); 231 [[fallthrough]]; 232 233 case 'l': 234 case 'L': 235 fmt::format_to(std::back_inserter(packet), ",{:x}", param); 236 break; 237 238 case 's': 239 // strings are written as {pointer}/{length} 240 fmt::format_to(std::back_inserter(packet), ",{:x}/{:x}", 241 static_cast<u32>(current_hio_request.parameters[i]), 242 current_hio_request.string_lengths[str_length_idx++]); 243 break; 244 245 default: 246 LOG_WARNING(Debug_GDBStub, "unexpected hio request param format '{}'", 247 current_hio_request.param_format[i]); 248 SendErrorReply(EILSEQ); 249 return false; 250 } 251 } 252 253 LOG_TRACE(Debug_GDBStub, "HIO request packet: '{}'", packet); 254 255 SendReply(packet.data()); 256 request_status = Status::SentWaitingReply; 257 return true; 258 } 259 260 } // namespace GDBStub