mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 17:55:23 +02:00
Lining a scripted repro up with a bug report ("about five seconds in, press
X") had no support at all. The only way to do it was to poll cpu.status in a
loop from the client, which is slow - a process spawn per poll, minutes for a
single run - and lands somewhere different every time, so the repro isn't one.
cpu.runUntilTime takes either an absolute `us` (as reported by cpu.status) or
`relativeUs` from now, resumes, and breaks when emulated time gets there. It
answers immediately with the target, and the usual cpu.stepping event follows
when it arrives. Anything else that stops the CPU first - a breakpoint, an
exception - cancels the deadline, the same way it cancels a pending step.
The deadline is held in microseconds, not ticks, and recomputed whenever
SetClockFrequencyHz() runs. Converting to a tick count once up front looks
right and isn't: games change the CPU clock while running, and CrossCraft
Classic goes 222 -> 333MHz during startup, which made a request for 3.0s stop
at 2.24s. With the recompute it stops at exactly 3000000us. Advance() also
shortens its slice to land on the deadline instead of up to a slice past it,
so repeated runs stop at the same instruction rather than somewhere in the
following frame.
Nothing is added to CoreTiming's event list, so savestates are unaffected -
the deadline is debugger session state and isn't serialized.
Also adds DebuggerRequest::ParamF64, since microseconds outgrow 32 bits after
about 71 minutes. Like the other Param* helpers it fails loudly on a missing
or unparseable value rather than defaulting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
126 lines
3.6 KiB
C++
126 lines
3.6 KiB
C++
// Copyright (c) 2018- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#pragma once
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include <string>
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/Data/Format/JSONReader.h"
|
|
#include "Common/Data/Format/JSONWriter.h"
|
|
#include "Common/Net/WebsocketServer.h"
|
|
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
// Enum name overlapped with UWP macro, quick hack to disable it
|
|
#undef OPTIONAL
|
|
#endif
|
|
|
|
using namespace json;
|
|
|
|
struct WebSocketClientInfo {
|
|
WebSocketClientInfo() = default;
|
|
|
|
std::string name;
|
|
std::string version;
|
|
std::map<std::string, bool> disallowed;
|
|
};
|
|
|
|
struct DebuggerErrorEvent {
|
|
DebuggerErrorEvent(const std::string m, LogLevel l, const JsonGet data = JsonValue(JSON_NULL))
|
|
: message(m), level(l) {
|
|
// Need to format right away, before it's out of scope.
|
|
if (data) {
|
|
const JsonNode *value = data.get("ticket");
|
|
if (value)
|
|
ticketRaw = json_stringify(value);
|
|
}
|
|
}
|
|
|
|
std::string message;
|
|
LogLevel level;
|
|
std::string ticketRaw;
|
|
|
|
operator std::string() const {
|
|
JsonWriter j;
|
|
j.begin();
|
|
j.writeString("event", "error");
|
|
j.writeString("message", message);
|
|
j.writeInt("level", (int)level);
|
|
if (!ticketRaw.empty()) {
|
|
j.writeRaw("ticket", ticketRaw);
|
|
}
|
|
j.end();
|
|
return j.str();
|
|
}
|
|
};
|
|
|
|
enum class DebuggerParamType {
|
|
REQUIRED,
|
|
OPTIONAL,
|
|
REQUIRED_LOOSE,
|
|
OPTIONAL_LOOSE,
|
|
};
|
|
|
|
struct DebuggerRequest {
|
|
DebuggerRequest(const char *n, net::WebSocketServer *w, const JsonGet &d, WebSocketClientInfo *client_info)
|
|
: name(n), ws(w), data(d), client(client_info) {
|
|
}
|
|
|
|
const char *name;
|
|
net::WebSocketServer *ws;
|
|
const JsonGet data;
|
|
WebSocketClientInfo *client;
|
|
|
|
void Fail(const std::string &message) {
|
|
ws->Send(DebuggerErrorEvent(message, LogLevel::LERROR, data));
|
|
responseSent_ = true;
|
|
}
|
|
|
|
bool HasParam(const char *name, bool ignoreNull = false);
|
|
bool ParamU32(const char *name, uint32_t *out, bool allowFloatBits = false, DebuggerParamType type = DebuggerParamType::REQUIRED);
|
|
// For quantities that don't fit in 32 bits, like emulated microseconds - JSON numbers are
|
|
// doubles anyway, which is exact well past any plausible session length.
|
|
bool ParamF64(const char *name, double *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
|
|
bool ParamBool(const char *name, bool *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
|
|
bool ParamString(const char *name, std::string *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
|
|
|
|
JsonWriter &Respond();
|
|
void Flush();
|
|
bool Finish();
|
|
|
|
private:
|
|
JsonWriter writer_;
|
|
bool responseBegun_ = false;
|
|
bool responseSent_ = false;
|
|
bool responsePartial_ = false;
|
|
};
|
|
|
|
class DebuggerSubscriber {
|
|
public:
|
|
virtual ~DebuggerSubscriber() {}
|
|
|
|
// Subscribers can also broadcast if they have simple cases to.
|
|
virtual void Broadcast(net::WebSocketServer *ws) {}
|
|
};
|
|
|
|
typedef std::function<void(DebuggerRequest &req)> DebuggerEventHandler;
|
|
typedef std::unordered_map<std::string_view, DebuggerEventHandler> DebuggerEventHandlerMap;
|
|
|
|
uint32_t RoundMemAddressUp(uint32_t addr);
|