Gdb debug server (#2542)

This commit is contained in:
Andrey 2017-04-02 20:10:06 +02:00 committed by Ivan
parent b54ba47870
commit 215a9f9e11
9 changed files with 1025 additions and 2 deletions

View file

@ -1,5 +1,8 @@
cmake_minimum_required(VERSION 2.8.12)
# uncomment next line if you want to build with GDB stub
# add_definitions(-DWITH_GDB_DEBUGGER)
set(ASMJIT_STATIC TRUE)
if (NOT CMAKE_BUILD_TYPE)

View file

@ -0,0 +1,820 @@
#include "stdafx.h"
#ifdef WITH_GDB_DEBUGGER
#include "GDBDebugServer.h"
#include "Log.h"
#include "Config.h"
#include <algorithm>
#include "Emu/Memory/Memory.h"
#include "Emu/System.h"
#include "Emu/IdManager.h"
#include "Emu/CPU/CPUThread.h"
#include "Emu/Cell/PPUThread.h"
#include "Emu/Cell/RawSPUThread.h"
#include "Emu/Cell/SPUThread.h"
#ifndef _WIN32
#include"fcntl.h"
#endif
extern void ppu_set_breakpoint(u32 addr);
extern void ppu_remove_breakpoint(u32 addr);
logs::channel gdbDebugServer("gdbDebugServer", logs::level::notice);
int sock_init(void)
{
#ifdef _WIN32
WSADATA wsa_data;
return WSAStartup(MAKEWORD(1, 1), &wsa_data);
#else
return 0;
#endif
}
int sock_quit(void)
{
#ifdef _WIN32
return WSACleanup();
#else
return 0;
#endif
}
#ifndef _WIN32
int closesocket(socket_t s) {
return close(s);
}
const int SOCKET_ERROR = -1;
const socket_t INVALID_SOCKET = -1;
#define sscanf_s sscanf
#define HEX_U32 "x"
#define HEX_U64 "lx"
#else
#define HEX_U32 "lx"
#define HEX_U64 "llx"
#endif
bool check_errno_again() {
#ifdef _WIN32
int err = GetLastError();
return (err == WSAEWOULDBLOCK);
#else
int err = errno;
return (err == EAGAIN) || (err == EWOULDBLOCK);
#endif
}
cfg::int_entry<1, 65535> g_cfg_gdb_server_port(cfg::root.misc, "Port", 2345);
std::string u32_to_hex(u32 i) {
return fmt::format("%" HEX_U32, i);
}
std::string u64_to_padded_hex(u64 value) {
return fmt::format("%.16" HEX_U64, value);
}
std::string u32_to_padded_hex(u32 value) {
return fmt::format("%.8" HEX_U32, value);
}
u8 hex_to_u8(std::string val) {
u8 result;
sscanf_s(val.c_str(), "%02hhX", &result);
return result;
}
u32 hex_to_u32(std::string val) {
u32 result;
sscanf_s(val.c_str(), "%" HEX_U32, &result);
return result;
}
u64 hex_to_u64(std::string val) {
u64 result;
sscanf_s(val.c_str(), "%" HEX_U64, &result);
return result;
}
void GDBDebugServer::start_server()
{
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == INVALID_SOCKET) {
gdbDebugServer.error("Error creating server socket");
return;
}
#ifdef WIN32
{
int mode = 1;
ioctlsocket(server_socket, FIONBIO, (u_long FAR *)&mode);
}
#else
fcntl(server_socket, F_SETFL, fcntl(server_socket, F_GETFL) | O_NONBLOCK);
#endif
int err;
sockaddr_in server_saddr;
server_saddr.sin_family = AF_INET;
int port = g_cfg_gdb_server_port;
server_saddr.sin_port = htons(port);
server_saddr.sin_addr.s_addr = htonl(INADDR_ANY);
err = bind(server_socket, (struct sockaddr *) &server_saddr, sizeof(server_saddr));
if (err == SOCKET_ERROR) {
gdbDebugServer.error("Error binding to port %d", port);
return;
}
err = listen(server_socket, 1);
if (err == SOCKET_ERROR) {
gdbDebugServer.error("Error listening on port %d", port);
return;
}
gdbDebugServer.success("GDB Debug Server listening on port %d", port);
}
int GDBDebugServer::read(void * buf, int cnt)
{
while (!stop) {
int result = recv(client_socket, reinterpret_cast<char*>(buf), cnt, 0);
if (result == SOCKET_ERROR) {
if (check_errno_again()) {
thread_ctrl::wait_for(50);
continue;
}
gdbDebugServer.error("Error during socket read");
fmt::throw_exception("Error during socket read" HERE);
}
return result;
}
return 0;
}
char GDBDebugServer::read_char()
{
char result;
int cnt = read(&result, 1);
if (!cnt) {
fmt::throw_exception("Tried to read char, but no data was available" HERE);
}
return result;
}
u8 GDBDebugServer::read_hexbyte()
{
char buf[2];
read(buf, 2);
return static_cast<u8>(strtol(buf, nullptr, 16));
}
void GDBDebugServer::try_read_cmd(gdb_cmd & out_cmd)
{
char c = read_char();
//interrupt
if (UNLIKELY(c == 0x03)) {
out_cmd.cmd = "\0x03";
out_cmd.data = "";
out_cmd.checksum = 0;
return;
}
if (UNLIKELY(c != '$')) {
//gdb starts conversation with + for some reason
if (c == '+') {
c = read_char();
}
if (c != '$') {
fmt::throw_exception("Expected start of packet character '$', got '%c' instead" HERE, c);
}
}
//clear packet data
out_cmd.cmd = "";
out_cmd.data = "";
out_cmd.checksum = 0;
bool cmd_part = true;
u8 checksum = 0;
while(true) {
c = read_char();
if (c == '#') {
break;
}
checksum = (checksum + reinterpret_cast<u8&>(c)) % 256;
//escaped char
if (c == '}') {
c = read_char() ^ 0x20;
checksum = (checksum + reinterpret_cast<u8&>(c)) % 256;
}
//cmd-data splitters
if (cmd_part && ((c == ':') || (c == '.') || (c == ';'))) {
cmd_part = false;
}
if (cmd_part) {
out_cmd.cmd += c;
//only q and v commands can have multi-char command
if ((out_cmd.cmd.length() == 1) && (c != 'q') && (c != 'v')) {
cmd_part = false;
}
} else {
out_cmd.data += c;
}
}
out_cmd.checksum = read_hexbyte();
if (out_cmd.checksum != checksum) {
throw new wrong_checksum_exception("Wrong checksum for packet" HERE);
}
}
bool GDBDebugServer::read_cmd(gdb_cmd & out_cmd)
{
while (true) {
try {
try_read_cmd(out_cmd);
ack(true);
return true;
}
catch (wrong_checksum_exception) {
ack(false);
}
catch (std::runtime_error e) {
gdbDebugServer.error(e.what());
return false;
}
}
}
void GDBDebugServer::send(const char * buf, int cnt)
{
gdbDebugServer.trace("Sending %s (%d bytes)", buf, cnt);
while (!stop) {
int res = ::send(client_socket, buf, cnt, 0);
if (res == SOCKET_ERROR) {
if (check_errno_again()) {
thread_ctrl::wait_for(50);
continue;
}
gdbDebugServer.error("Failed sending %d bytes", cnt);
return;
}
return;
}
}
void GDBDebugServer::send_char(char c)
{
send(&c, 1);
}
void GDBDebugServer::ack(bool accepted)
{
send_char(accepted ? '+' : '-');
}
void GDBDebugServer::send_cmd(const std::string & cmd)
{
u8 checksum = 0;
std::string buf;
buf.reserve(cmd.length() + 4);
buf += "$";
for (int i = 0; i < cmd.length(); ++i) {
checksum = (checksum + append_encoded_char(cmd[i], buf)) % 256;
}
buf += "#";
buf += to_hexbyte(checksum);
send(buf.c_str(), static_cast<int>(buf.length()));
}
bool GDBDebugServer::send_cmd_ack(const std::string & cmd)
{
while (true) {
send_cmd(cmd);
char c = read_char();
if (LIKELY(c == '+')) {
return true;
}
if (UNLIKELY(c != '-')) {
gdbDebugServer.error("Wrong acknowledge character received %c", c);
return false;
}
gdbDebugServer.warning("Client rejected our cmd");
}
}
u8 GDBDebugServer::append_encoded_char(char c, std::string & str)
{
u8 checksum = 0;
if (UNLIKELY((c == '#') || (c == '$') || (c == '}'))) {
str += '}';
c ^= 0x20;
checksum = '}';
}
checksum = (checksum + reinterpret_cast<u8&>(c)) % 256;
str += c;
return checksum;
}
std::string GDBDebugServer::to_hexbyte(u8 i)
{
std::string result = "00";
u8 i1 = i & 0xF;
u8 i2 = i >> 4;
result[0] = i2 > 9 ? 'a' + i2 - 10 : '0' + i2;
result[1] = i1 > 9 ? 'a' + i1 - 10 : '0' + i1;
return result;
}
bool GDBDebugServer::select_thread(u64 id)
{
//in case we have none at all
selected_thread.reset();
const auto on_select = [&](u32, cpu_thread& cpu)
{
return (id == ALL_THREADS) || (id == ANY_THREAD) || (cpu.id == id);
};
if (auto ppu = idm::select<ppu_thread>(on_select)) {
selected_thread = ppu.ptr;
return true;
}
return false;
}
std::string GDBDebugServer::get_reg(std::shared_ptr<ppu_thread> thread, u32 rid)
{
std::string result;
//ids from gdb/features/rs6000/powerpc-64.c
//pc
switch (rid) {
case 64:
return u64_to_padded_hex(thread->cia);
//msr?
case 65:
return std::string(16, 'x');
case 66:
return u32_to_padded_hex(thread->cr_pack());
case 67:
return u64_to_padded_hex(thread->lr);
case 68:
return u64_to_padded_hex(thread->ctr);
//xer
case 69:
return std::string(8, 'x');
//fpscr
case 70:
return std::string(8, 'x');
default:
if (rid > 70) return "";
return (rid > 31)
? u64_to_padded_hex(reinterpret_cast<u64&>(thread->fpr[rid - 32])) //fpr
: u64_to_padded_hex(thread->gpr[rid]); //gpr
}
}
bool GDBDebugServer::set_reg(std::shared_ptr<ppu_thread> thread, u32 rid, std::string value)
{
switch (rid) {
case 64:
thread->cia = static_cast<u32>(hex_to_u64(value));
return true;
//msr?
case 65:
return true;
case 66:
thread->cr_unpack(hex_to_u32(value));
return true;
case 67:
thread->lr = hex_to_u64(value);
return true;
case 68:
thread->ctr = hex_to_u64(value);
return true;
//xer
case 69:
return true;
//fpscr
case 70:
return true;
default:
if (rid > 70) return false;
if (rid > 31) {
u64 val = hex_to_u64(value);
thread->fpr[rid - 32] = reinterpret_cast<f64&>(val);
} else {
thread->gpr[rid] = hex_to_u64(value);
}
return true;
}
}
u32 GDBDebugServer::get_reg_size(std::shared_ptr<ppu_thread> thread, u32 rid)
{
switch (rid) {
case 66:
case 69:
case 70:
return 4;
default:
if (rid > 70) {
return 0;
}
return 8;
}
}
bool GDBDebugServer::send_reason()
{
return send_cmd_ack("S05");
}
bool GDBDebugServer::cmd_extended_mode(gdb_cmd & cmd)
{
return send_cmd_ack("OK");
}
bool GDBDebugServer::cmd_reason(gdb_cmd & cmd)
{
return send_reason();
}
bool GDBDebugServer::cmd_supported(gdb_cmd & cmd)
{
return send_cmd_ack("PacketSize=1200");
}
bool GDBDebugServer::cmd_thread_info(gdb_cmd & cmd)
{
std::string result = "";
const auto on_select = [&](u32, cpu_thread& cpu)
{
if (result.length()) {
result += ",";
}
result += u64_to_padded_hex(static_cast<u64>(cpu.id));
};
idm::select<ppu_thread>(on_select);
idm::select<ARMv7Thread>(on_select);
idm::select<RawSPUThread>(on_select);
idm::select<SPUThread>(on_select);
//todo: this may exceed max command length
result = "m" + result + "l";
return send_cmd_ack(result);;
}
bool GDBDebugServer::cmd_current_thread(gdb_cmd & cmd)
{
return send_cmd_ack(selected_thread.expired() ? "" : u64_to_padded_hex(selected_thread.lock()->id));
}
bool GDBDebugServer::cmd_read_register(gdb_cmd & cmd)
{
select_thread(general_ops_thread_id);
auto th = selected_thread.lock();
if (th->id_type() == 1) {
auto ppu = std::static_pointer_cast<ppu_thread>(th);
u32 rid = hex_to_u32(cmd.data);
std::string result = get_reg(ppu, rid);
if (!result.length()) {
gdbDebugServer.warning("Wrong register id %d", rid);
return send_cmd_ack("E01");
}
return send_cmd_ack(result);
}
gdbDebugServer.warning("Unimplemented thread type %d", th->id_type());
return send_cmd_ack("");
}
bool GDBDebugServer::cmd_write_register(gdb_cmd & cmd)
{
select_thread(general_ops_thread_id);
auto th = selected_thread.lock();
if (th->id_type() == 1) {
auto ppu = std::static_pointer_cast<ppu_thread>(th);
size_t eq_pos = cmd.data.find('=');
if (eq_pos == std::string::npos) {
gdbDebugServer.warning("Wrong write_register cmd data %s", cmd.data.c_str());
return send_cmd_ack("E02");
}
u32 rid = hex_to_u32(cmd.data.substr(0, eq_pos));
std::string value = cmd.data.substr(eq_pos + 1);
if (!set_reg(ppu, rid, value)) {
gdbDebugServer.warning("Wrong register id %d", rid);
return send_cmd_ack("E01");
}
return send_cmd_ack("OK");
}
gdbDebugServer.warning("Unimplemented thread type %d", th->id_type());
return send_cmd_ack("");
}
bool GDBDebugServer::cmd_read_memory(gdb_cmd & cmd)
{
size_t s = cmd.data.find(',');
u32 addr = hex_to_u32(cmd.data.substr(0, s));
u32 len = hex_to_u32(cmd.data.substr(s + 1));
std::string result;
result.reserve(len * 2);
for (u32 i = 0; i < len; ++i) {
if (vm::check_addr(addr + i)) {
result += to_hexbyte(vm::read8(addr + i));
} else {
break;
//result += "xx";
}
}
if (len && !result.length()) {
//nothing read
return send_cmd_ack("E01");
}
return send_cmd_ack(result);
}
bool GDBDebugServer::cmd_write_memory(gdb_cmd & cmd)
{
size_t s = cmd.data.find(',');
size_t s2 = cmd.data.find(':');
if ((s == std::string::npos) || (s2 == std::string::npos)) {
gdbDebugServer.warning("Malformed write memory request received: %s", cmd.data.c_str());
return send_cmd_ack("E01");
}
u32 addr = hex_to_u32(cmd.data.substr(0, s));
u32 len = hex_to_u32(cmd.data.substr(s + 1, s2 - s - 1));
const char* data_ptr = (cmd.data.c_str()) + s2 + 1;
for (u32 i = 0; i < len; ++i) {
if (vm::check_addr(addr + i)) {
u8 val;
int res = sscanf_s(data_ptr, "%02hhX", &val);
if (!res) {
gdbDebugServer.warning("Couldn't read u8 from string %s", data_ptr);
return send_cmd_ack("E02");
}
data_ptr += 2;
vm::write8(addr + i, val);
} else {
return send_cmd_ack("E03");
}
}
return send_cmd_ack("OK");
}
bool GDBDebugServer::cmd_read_all_registers(gdb_cmd & cmd)
{
std::string result;
select_thread(general_ops_thread_id);
auto th = selected_thread.lock();
if (th->id_type() == 1) {
auto ppu = std::static_pointer_cast<ppu_thread>(th);
//68 64-bit registers, and 3 32-bit
result.reserve(68*16 + 3*8);
for (int i = 0; i < 71; ++i) {
result += get_reg(ppu, i);
}
return send_cmd_ack(result);
}
gdbDebugServer.warning("Unimplemented thread type %d", th->id_type());
return send_cmd_ack("");
}
bool GDBDebugServer::cmd_write_all_registers(gdb_cmd & cmd)
{
select_thread(general_ops_thread_id);
auto th = selected_thread.lock();
if (th->id_type() == 1) {
auto ppu = std::static_pointer_cast<ppu_thread>(th);
int ptr = 0;
for (int i = 0; i < 71; ++i) {
int sz = get_reg_size(ppu, i);
set_reg(ppu, i, cmd.data.substr(ptr, sz * 2));
ptr += sz * 2;
}
return send_cmd_ack("OK");
}
gdbDebugServer.warning("Unimplemented thread type %d", th->id_type());
return send_cmd_ack("E01");
}
bool GDBDebugServer::cmd_set_thread_ops(gdb_cmd & cmd)
{
char type = cmd.data[0];
std::string thread = cmd.data.substr(1);
u64 id = thread == "-1" ? ALL_THREADS : hex_to_u64(thread);
if (type == 'c') {
continue_ops_thread_id = id;
} else {
general_ops_thread_id = id;
}
if (select_thread(id)) {
return send_cmd_ack("OK");
}
gdbDebugServer.warning("Client asked to use thread %llx for %s, but no matching thread was found", id, type == 'c' ? "continue ops" : "general ops");
return send_cmd_ack("E01");
}
bool GDBDebugServer::cmd_attached_to_what(gdb_cmd & cmd)
{
//creating processes from client is not available yet
return send_cmd_ack("1");
}
bool GDBDebugServer::cmd_kill(gdb_cmd & cmd)
{
Emu.Stop();
return true;
}
bool GDBDebugServer::cmd_continue_support(gdb_cmd & cmd)
{
return send_cmd_ack("vCont;c;s;C;S");
}
bool GDBDebugServer::cmd_vcont(gdb_cmd & cmd)
{
//todo: handle multiple actions and thread ids
this->from_breakpoint = false;
if (cmd.data[1] == 'c') {
select_thread(continue_ops_thread_id);
auto ppu = std::static_pointer_cast<ppu_thread>(selected_thread.lock());
ppu->state -= cpu_flag::dbg_pause;
if (Emu.IsPaused()) {
Emu.Resume();
}
thread_ctrl::wait();
//we are in all-stop mode
Emu.Pause();
return send_reason();
} else if (cmd.data[1] == 's') {
select_thread(continue_ops_thread_id);
auto ppu = std::static_pointer_cast<ppu_thread>(selected_thread.lock());
ppu->state += cpu_flag::dbg_step;
ppu->state -= cpu_flag::dbg_pause;
if (Emu.IsPaused()) {
Emu.Resume();
} else {
ppu->notify();
}
thread_ctrl::wait();
//we are in all-stop mode
Emu.Pause();
return send_reason();
}
return send_cmd_ack("");
}
static const u32 INVALID_PTR = 0xffffffff;
bool GDBDebugServer::cmd_set_breakpoint(gdb_cmd & cmd)
{
char type = cmd.data[0];
//software breakpoint
if (type == '0') {
u32 addr = INVALID_PTR;
if (cmd.data.find(';') != std::string::npos) {
gdbDebugServer.warning("Received request to set breakpoint with condition, but they are not supported");
return send_cmd_ack("E01");
}
sscanf_s(cmd.data.c_str(), "0,%x", &addr);
if (addr == INVALID_PTR) {
gdbDebugServer.warning("Can't parse breakpoint request, data: %s", cmd.data.c_str());
return send_cmd_ack("E02");
}
ppu_set_breakpoint(addr);
return send_cmd_ack("OK");
}
//other breakpoint types are not supported
return send_cmd_ack("");
}
bool GDBDebugServer::cmd_remove_breakpoint(gdb_cmd & cmd)
{
char type = cmd.data[0];
//software breakpoint
if (type == '0') {
u32 addr = INVALID_PTR;
sscanf_s(cmd.data.c_str(), "0,%x", &addr);
if (addr == INVALID_PTR) {
gdbDebugServer.warning("Can't parse breakpoint remove request, data: %s", cmd.data.c_str());
return send_cmd_ack("E01");
}
ppu_remove_breakpoint(addr);
return send_cmd_ack("OK");
}
//other breakpoint types are not supported
return send_cmd_ack("");
}
#define PROCESS_CMD(cmds,handler) if (cmd.cmd == cmds) { if (!handler(cmd)) break; else continue; }
void GDBDebugServer::on_task()
{
sock_init();
start_server();
while (!stop) {
sockaddr_in client;
socklen_t client_len = sizeof(client);
client_socket = accept(server_socket, (struct sockaddr *) &client, &client_len);
if (client_socket == INVALID_SOCKET) {
if (check_errno_again()) {
thread_ctrl::wait_for(50);
continue;
}
gdbDebugServer.error("Could not establish new connection\n");
return;
}
//stop immediately
Emu.Pause();
try {
char hostbuf[32];
inet_ntop(client.sin_family, reinterpret_cast<void*>(&client.sin_addr), hostbuf, 32);
gdbDebugServer.success("Got connection to GDB debug server from %s:%d", hostbuf, client.sin_port);
gdb_cmd cmd;
while (!stop) {
if (!read_cmd(cmd)) {
break;
}
gdbDebugServer.trace("Command %s with data %s received", cmd.cmd.c_str(), cmd.data.c_str());
PROCESS_CMD("!", cmd_extended_mode);
PROCESS_CMD("?", cmd_reason);
PROCESS_CMD("qSupported", cmd_supported);
PROCESS_CMD("qfThreadInfo", cmd_thread_info);
PROCESS_CMD("qC", cmd_current_thread);
PROCESS_CMD("p", cmd_read_register);
PROCESS_CMD("P", cmd_write_register);
PROCESS_CMD("m", cmd_read_memory);
PROCESS_CMD("M", cmd_write_memory);
PROCESS_CMD("g", cmd_read_all_registers);
PROCESS_CMD("G", cmd_write_all_registers);
PROCESS_CMD("H", cmd_set_thread_ops);
PROCESS_CMD("qAttached", cmd_attached_to_what);
PROCESS_CMD("k", cmd_kill);
PROCESS_CMD("vCont?", cmd_continue_support);
PROCESS_CMD("vCont", cmd_vcont);
PROCESS_CMD("z", cmd_remove_breakpoint);
PROCESS_CMD("Z", cmd_set_breakpoint);
gdbDebugServer.trace("Unsupported command received %s", cmd.cmd.c_str());
if (!send_cmd_ack("")) {
break;
}
}
}
catch (std::runtime_error& e)
{
if (client_socket) {
closesocket(client_socket);
client_socket = 0;
}
gdbDebugServer.error(e.what());
}
}
}
#undef PROCESS_CMD
void GDBDebugServer::on_exit()
{
if (server_socket) {
closesocket(server_socket);
}
if (client_socket) {
closesocket(client_socket);
}
sock_quit();
}
std::string GDBDebugServer::get_name() const
{
return "GDBDebugger";
}
void GDBDebugServer::on_stop()
{
this->stop = true;
//just in case we are waiting for breakpoint
this->notify();
named_thread::on_stop();
}
u32 g_gdb_debugger_id = 0;
#ifndef _WIN32
#undef sscanf_s
#endif
#undef HEX_U32
#undef HEX_U64
#endif

131
Utilities/GDBDebugServer.h Normal file
View file

@ -0,0 +1,131 @@
#pragma once
#ifdef WITH_GDB_DEBUGGER
#include "Thread.h"
#include <Emu/IdManager.h>
#include "Emu/CPU/CPUThread.h"
#include "Emu/Cell/PPUThread.h"
#ifdef _WIN32
#include <winsock2.h>
#include <WS2tcpip.h>
#else
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif
#ifdef _WIN32
using socket_t = SOCKET;
#else
using socket_t = int;
#endif
typedef struct gdb_cmd {
std::string cmd;
std::string data;
u8 checksum;
} gdb_cmd;
class wrong_checksum_exception : public std::runtime_error {
public:
wrong_checksum_exception(char const* const message) : runtime_error(message) {}
};
const u64 ALL_THREADS = 0xffffffffffffffff;
const u64 ANY_THREAD = 0;
class GDBDebugServer : public named_thread {
socket_t server_socket;
socket_t client_socket;
std::weak_ptr<cpu_thread> selected_thread;
u64 continue_ops_thread_id = ANY_THREAD;
u64 general_ops_thread_id = ANY_THREAD;
//initialize server socket and start listening
void start_server();
//read at most cnt bytes to buf, returns nubmer of bytes actually read
int read(void* buf, int cnt);
//reads one character
char read_char();
//reads pairs of hex characters and returns their integer value
u8 read_hexbyte();
//tries to read command, throws exceptions if anything goes wrong
void try_read_cmd(gdb_cmd& out_cmd);
//reads commands until receiveing one with valid checksum
//in case of other exception (i.e. wrong first char of command)
//it will log exception text and return false
//in that case best for caller would be to stop reading, because
//chance of getting correct command is low
bool read_cmd(gdb_cmd& out_cmd);
//send cnt bytes from buf to client
void send(const char* buf, int cnt);
//send character to client
void send_char(char c);
//acknowledge packet, either as accepted or declined
void ack(bool accepted);
//sends command body cmd to client
void send_cmd(const std::string & cmd);
//sends command to client until receives positive acknowledgement
//returns false in case some error happened, and command wasn't sent
bool send_cmd_ack(const std::string & cmd);
//appends encoded char c to string str, and returns checksum. encoded byte can occupy 2 bytes
static u8 append_encoded_char(char c, std::string& str);
//convert u8 to 2 byte hexademical representation
static std::string to_hexbyte(u8 i);
//choose thread, support ALL_THREADS and ANY_THREAD values, returns true if some thread was selected
bool select_thread(u64 id);
//returns register value as hex string by register id (in gdb), in case of wrong id returns empty string
static std::string get_reg(std::shared_ptr<ppu_thread> thread, u32 rid);
//sets register value to hex string by register id (in gdb), in case of wrong id returns false
static bool set_reg(std::shared_ptr<ppu_thread> thread, u32 rid, std::string value);
//returns size of register with id rid in bytes, zero if invalid rid is provided
static u32 get_reg_size(std::shared_ptr<ppu_thread> thread, u32 rid);
//send reason of stop, returns false if sending response failed
bool send_reason();
//commands
bool cmd_extended_mode(gdb_cmd& cmd);
bool cmd_reason(gdb_cmd& cmd);
bool cmd_supported(gdb_cmd& cmd);
bool cmd_thread_info(gdb_cmd& cmd);
bool cmd_current_thread(gdb_cmd& cmd);
bool cmd_read_register(gdb_cmd& cmd);
bool cmd_write_register(gdb_cmd& cmd);
bool cmd_read_memory(gdb_cmd& cmd);
bool cmd_write_memory(gdb_cmd& cmd);
bool cmd_read_all_registers(gdb_cmd& cmd);
bool cmd_write_all_registers(gdb_cmd& cmd);
bool cmd_set_thread_ops(gdb_cmd& cmd);
bool cmd_attached_to_what(gdb_cmd& cmd);
bool cmd_kill(gdb_cmd& cmd);
bool cmd_continue_support(gdb_cmd& cmd);
bool cmd_vcont(gdb_cmd& cmd);
bool cmd_set_breakpoint(gdb_cmd& cmd);
bool cmd_remove_breakpoint(gdb_cmd& cmd);
protected:
void on_task() override final;
void on_exit() override final;
public:
static const u32 id_base = 1;
static const u32 id_step = 1;
static const u32 id_count = 0x100000;
bool from_breakpoint = true;
bool stop = false;
virtual std::string get_name() const;
virtual void on_stop() override final;
};
extern u32 g_gdb_debugger_id;
#endif

View file

@ -2,6 +2,8 @@
#include "Emu/System.h"
#include "Emu/Memory/vm.h"
#include "CPUThread.h"
#include "Emu/IdManager.h"
#include "Utilities/GDBDebugServer.h"
DECLARE(cpu_thread::g_threads_created){0};
DECLARE(cpu_thread::g_threads_deleted){0};
@ -92,6 +94,12 @@ cpu_thread::cpu_thread(u32 id)
bool cpu_thread::check_state()
{
#ifdef WITH_GDB_DEBUGGER
if (test(state, cpu_flag::dbg_pause)) {
fxm::get<GDBDebugServer>()->notify();
}
#endif
bool cpu_sleep_called = false;
bool cpu_flag_memory = false;

View file

@ -112,6 +112,9 @@ LOG_CHANNEL(sys_libc);
LOG_CHANNEL(sys_lv2dbg);
LOG_CHANNEL(libnet);
LOG_CHANNEL(sysPrxForUser);
#ifdef WITH_GDB_DEBUGGER
LOG_CHANNEL(gdbDebugServer);
#endif
cfg::bool_entry g_cfg_hook_ppu_funcs(cfg::root.core, "Hook static functions");
cfg::bool_entry g_cfg_load_liblv2(cfg::root.core, "Load liblv2.sprx only");

View file

@ -11,6 +11,7 @@
#include "PPUModule.h"
#include "lv2/sys_sync.h"
#include "lv2/sys_prx.h"
#include "Utilities/GDBDebugServer.h"
#ifdef LLVM_AVAILABLE
#include "restore_new.h"
@ -195,7 +196,11 @@ extern void ppu_register_function_at(u32 addr, u32 size, ppu_function_t ptr)
static bool ppu_break(ppu_thread& ppu, ppu_opcode_t op)
{
// Pause and wait if necessary
if (!ppu.state.test_and_set(cpu_flag::dbg_pause) && ppu.check_state())
bool status = ppu.state.test_and_set(cpu_flag::dbg_pause);
#ifdef WITH_GDB_DEBUGGER
fxm::get<GDBDebugServer>()->notify();
#endif
if (!status && ppu.check_state())
{
return false;
}
@ -248,6 +253,38 @@ void ppu_thread::on_init(const std::shared_ptr<void>& _this)
}
}
//sets breakpoint, does nothing if there is a breakpoint there already
extern void ppu_set_breakpoint(u32 addr)
{
if (g_cfg_ppu_decoder.get() == ppu_decoder_type::llvm)
{
return;
}
const auto _break = ::narrow<u32>(reinterpret_cast<std::uintptr_t>(&ppu_break));
if (ppu_ref(addr / 4) != _break)
{
ppu_ref(addr / 4) = _break;
}
}
//removes breakpoint, does nothing if there is no breakpoint at location
extern void ppu_remove_breakpoint(u32 addr)
{
if (g_cfg_ppu_decoder.get() == ppu_decoder_type::llvm)
{
return;
}
const auto _break = ::narrow<u32>(reinterpret_cast<std::uintptr_t>(&ppu_break));
if (ppu_ref(addr / 4) == _break)
{
ppu_ref(addr / 4) = ppu_cache(addr);
}
}
std::string ppu_thread::get_name() const
{
return fmt::format("PPU[0x%x] Thread (%s)", id, m_name);

View file

@ -26,6 +26,8 @@
#include <thread>
#include "Utilities/GDBDebugServer.h"
system_type g_system;
cfg::bool_entry g_cfg_autostart(cfg::root.misc, "Always start after boot", true);
@ -100,7 +102,10 @@ void Emulator::Init()
fs::create_dir(dev_hdd1 + "game/");
fs::create_path(dev_hdd1);
fs::create_path(dev_usb);
#ifdef WITH_GDB_DEBUGGER
fxm::make<GDBDebugServer>();
#endif
// Initialize patch engine
fxm::make_always<patch_engine>()->append(fs::get_config_dir() + "/patch.yml");
}
@ -488,6 +493,12 @@ void Emulator::Stop()
rpcs3::on_stop()();
#ifdef WITH_GDB_DEBUGGER
//fxm for some reason doesn't call on_stop
fxm::get<GDBDebugServer>()->on_stop();
fxm::remove<GDBDebugServer>();
#endif
auto e_stop = std::make_exception_ptr(cpu_flag::dbg_global_stop);
auto on_select = [&](u32, cpu_thread& cpu)

View file

@ -80,6 +80,9 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Utilities\dynamic_library.cpp" />
<ClCompile Include="..\Utilities\GDBDebugServer.cpp">
<PrecompiledHeader>Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Utilities\JIT.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
@ -406,6 +409,7 @@
<ClInclude Include="..\Utilities\cond.h" />
<ClInclude Include="..\Utilities\dynamic_library.h" />
<ClInclude Include="..\Utilities\event.h" />
<ClInclude Include="..\Utilities\GDBDebugServer.h" />
<ClInclude Include="..\Utilities\geometry.h" />
<ClInclude Include="..\Utilities\GSL.h" />
<ClInclude Include="..\Utilities\JIT.h" />

View file

@ -908,6 +908,9 @@
<ClCompile Include="Loader\TAR.cpp">
<Filter>Loader</Filter>
</ClCompile>
<ClCompile Include="..\Utilities\GDBDebugServer.cpp">
<Filter>Utilities</Filter>
</ClCompile>
<ClCompile Include="..\Utilities\bin_patch.cpp">
<Filter>Utilities</Filter>
</ClCompile>
@ -1741,6 +1744,9 @@
<ClInclude Include="Loader\TAR.h">
<Filter>Loader</Filter>
</ClInclude>
<ClInclude Include="..\Utilities\GDBDebugServer.h">
<Filter>Utilities</Filter>
</ClInclude>
<ClInclude Include="..\Utilities\bin_patch.h">
<Filter>Utilities</Filter>
</ClInclude>