pureikyubu/src/debug.cpp
2023-08-19 23:20:50 +03:00

707 lines
14 KiB
C++

#include "pch.h"
namespace Debug
{
ReportHub Msgs; // Singletone
void Halt(const char* text, ...)
{
va_list arg;
char buf[0x1000] = { 0, };
va_start(arg, text);
vsprintf(buf, text, arg);
va_end(arg);
Msgs.AddReport(Channel::Error, true, buf);
bool any_debugger_present = false;
if (Debug::debugger != nullptr) {
Debug::debugger->InvalidateAll();
any_debugger_present = true;
}
if (!any_debugger_present) {
Jdi->ExecuteCommand(
std::string("UIReport \"The emulation is crashed. Details can be viewed in the debugger (Ctrl+D)\n\n" + std::string(buf) + "\"").c_str());
}
}
void Report(Channel chan, const char* text, ...)
{
if (chan == Channel::Void)
{
return;
}
va_list arg;
char buf[0x1000] = { 0, };
va_start(arg, text);
vsprintf(buf, text, arg);
va_end(arg);
Msgs.AddReport(chan, false, buf);
}
ReportHub::ReportHub()
{
}
ReportHub::~ReportHub()
{
Flush(true);
}
void ReportHub::Flush(bool lockable)
{
if (lockable)
{
reportLock.Lock();
}
while (!reportQueue.empty())
{
ReportEntry* entry = reportQueue.back();
reportQueue.pop_back();
delete entry;
}
if (lockable)
{
reportLock.Unlock();
}
}
/// <summary>
/// Get the human-readable name of a debug channel
/// </summary>
/// <param name="chan"></param>
/// <returns></returns>
std::string ReportHub::DebugChannelToString(Channel chan)
{
switch (chan)
{
case Channel::Norm: return "";
case Channel::Info: return "Info";
case Channel::Error: return "Error";
case Channel::Header: return "Header";
case Channel::CP: return "CP";
case Channel::PE: return "PE";
case Channel::VI: return "VI";
case Channel::GP: return "GP";
case Channel::PI: return "PI";
case Channel::CPU: return "CPU";
case Channel::MI: return "MI";
case Channel::DSP: return "DSP";
case Channel::DI: return "DI";
case Channel::AR: return "AR";
case Channel::AI: return "AI";
case Channel::AIS: return "AIS";
case Channel::SI: return "SI";
case Channel::EXI: return "EXI";
case Channel::MC: return "MC";
case Channel::DVD: return "DVD";
case Channel::AX: return "AX";
case Channel::Loader: return "Loader";
case Channel::HLE: return "HLE";
}
return "Unknown";
}
/// <summary>
/// Get history of debug messages (oldest first). Clear queue in progress.
/// </summary>
/// <param name="queue"></param>
void ReportHub::QueryDebugMessages(std::list<std::pair<Channel, std::string>>& queue)
{
reportLock.Lock();
if (reportQueue.size() != 0)
{
queue.clear();
while (!reportQueue.empty())
{
ReportEntry* entry = reportQueue.back();
reportQueue.pop_back();
queue.push_front(std::pair<Channel, std::string>(entry->savedChan, entry->text));
delete entry;
}
}
reportLock.Unlock();
}
/// <summary>
/// Add a debug message. Stop emulation if necessary.
/// </summary>
/// <param name="chan"></param>
/// <param name="haltCpu"></param>
/// <param name="text"></param>
void ReportHub::AddReport(Channel chan, bool haltCpu, const std::string& text)
{
if (chan == Channel::Void)
{
return;
}
reportLock.Lock();
// If no one reads the message history, it periodically cleans itself.
if (reportQueue.size() >= MessageLimit)
{
// No need to lock, already locked
Flush(false);
}
ReportEntry* entry = new ReportEntry(chan, text);
reportQueue.push_back(entry);
reportLock.Unlock();
// Stop emulating the entire system on demand
if (haltCpu)
{
if (Core->IsRunning())
{
Core->Suspend();
}
}
}
}
namespace Debug
{
void SamplingProfiler::ThreadProc(void* Parameter)
{
SamplingProfiler* profiler = (SamplingProfiler*)Parameter;
uint64_t ticks = Core->GetTicks();
if (ticks >= (profiler->savedGekkoTbr + profiler->pollingInterval))
{
profiler->savedGekkoTbr = ticks;
profiler->sampleData->AddUInt64(nullptr, ticks);
profiler->sampleData->AddUInt32(nullptr, Core->regs.pc);
}
}
SamplingProfiler::SamplingProfiler(const char* jsonFileName, int periodMs)
{
filename = jsonFileName;
pollingInterval = periodMs * (Core->OneSecond() / 1000);
savedGekkoTbr = Core->GetTicks();
json = new Json();
rootObj = json->root.AddObject(nullptr);
assert(rootObj);
sampleData = rootObj->AddArray("sampleData");
assert(sampleData);
thread = EMUCreateThread(ThreadProc, false, this, "SamplingProfiler");
}
SamplingProfiler::~SamplingProfiler()
{
EMUJoinThread(thread);
size_t textSize = 0;
json->GetSerializedTextSize(nullptr, -1, textSize);
uint8_t* jsonText = new uint8_t[2 * textSize];
assert(jsonText);
json->Serialize(jsonText, 2 * textSize, textSize);
auto buffer = std::vector<uint8_t>(jsonText, jsonText + textSize);
Util::FileSave(filename, buffer);
delete[] jsonText;
delete json;
}
}
// Debug commands
#define my_max(a,b) (((a) > (b)) ? (a) : (b))
#define my_min(a,b) (((a) < (b)) ? (a) : (b))
namespace Debug
{
static bool testempty(char* str)
{
int len = (int)strlen(str);
for (int i = 0; i < len; i++)
{
if (str[i] > ' ') return false;
}
return true;
}
static void Tokenize(char* line, std::vector<std::string>& args)
{
#define endl ( line[p] == 0 )
#define space ( line[p] == 0x20 )
#define quot ( line[p] == '\'' )
#define dquot ( line[p] == '\"' )
int p, start, end;
p = start = end = 0;
args.clear();
// while not end line
while (!endl)
{
// skip space first, if any
while (space) p++;
if (!endl && (quot || dquot))
{ // quotation, need special case
p++;
start = p;
while (1)
{
if (endl)
{
throw "Open quotation";
return;
}
if (quot || dquot)
{
end = p;
p++;
break;
}
else p++;
}
args.push_back(std::string(line + start, end - start));
}
else if (!endl)
{
start = p;
while (1)
{
if (endl || space || quot || dquot)
{
end = p;
break;
}
p++;
}
args.push_back(std::string(line + start, end - start));
}
}
#undef space
#undef quot
#undef dquot
#undef endl
}
static Json::Value* cmd_script(std::vector<std::string>& args)
{
size_t i;
const char* file;
std::vector<std::string> commandArgs;
file = args[1].c_str();
Report(Channel::Norm, "Loading script: %s\n", file);
auto sbuf = Util::FileLoad((std::string)file);
if (sbuf.empty())
{
Report(Channel::Norm, "Cannot open script file!\n");
return nullptr;
}
sbuf.push_back(0);
/* Remove all garbage, like tabs. */
for (i = 0; i < sbuf.size(); i++)
{
char c = sbuf[i];
if (c < ' ')
{
c = '\n';
}
}
Report(Channel::Norm, "Executing script...\n");
int cnt = 1;
char* ptr = (char*)sbuf.data();
while (*ptr)
{
char line[1000];
line[i = 0] = 0;
// Cut string
while (*ptr == '\n') ptr++;
if (!*ptr) break;
while (*ptr != '\n') line[i++] = *ptr++;
line[i++] = 0;
// remove comments
char* p = line;
while (*p)
{
if (p[0] == '/' && p[1] == '/')
{
*p = 0;
break;
}
p++;
}
// empty string ?
if (strlen(line) == 0) continue;
// remove spaces at the end
p = &line[strlen(line) - 1];
while (*p <= ' ') p--;
if (*p) p[1] = 0;
// remove spaces at the beginning
p = line;
while (*p <= ' ' && *p) p++;
// empty string ?
if (!*p) continue;
// execute line
if (testempty(line)) continue;
Report(Channel::Norm, "%i: %s", cnt++, line);
commandArgs.clear();
Tokenize(line, commandArgs);
line[0] = 0;
JDI::Hub.Execute(commandArgs);
}
Report(Channel::Norm, "\nDone execute script.\n");
return nullptr;
}
// Echo
static Json::Value* cmd_echo(std::vector<std::string>& args)
{
std::string text = "";
for (size_t i = 1; i < args.size(); i++)
{
text += args[i] + " ";
}
Report(Channel::Norm, "%s\n", text.c_str());
return nullptr;
}
static SamplingProfiler* profiler = nullptr;
static Json::Value* StartProfiler(std::vector<std::string>& args)
{
if (profiler)
{
Report(Channel::Norm, "Already started.\n");
return nullptr;
}
int period = 5;
if (args.size() > 2)
{
period = atoi(args[2].c_str());
period = my_min(2, my_max(period, 50));
}
profiler = new SamplingProfiler(args[1].c_str(), period);
Report(Channel::Norm, "Profiler started.\n");
return nullptr;
}
static Json::Value* StopProfiler(std::vector<std::string>& args)
{
if (profiler == nullptr)
{
Report(Channel::Norm, "Not started.\n");
return nullptr;
}
delete profiler;
profiler = nullptr;
Report(Channel::Norm, "Profiler stopped.\n");
return nullptr;
}
static Json::Value* GetChannelName(std::vector<std::string>& args)
{
Channel chan = (Channel)atoi(args[1].c_str());
Json::Value* output = new Json::Value();
output->type = Json::ValueType::Array;
output->AddString(nullptr, Util::StringToWstring(Msgs.DebugChannelToString(chan)).c_str());
return output;
}
static Json::Value* QueryDebugMessages(std::vector<std::string>& args)
{
Json::Value* output = new Json::Value();
output->type = Json::ValueType::Array;
std::list<std::pair<Channel, std::string>> queue;
Msgs.QueryDebugMessages(queue);
for (auto it = queue.begin(); it != queue.end(); ++it)
{
output->AddInt(nullptr, (int)it->first);
output->AddString(nullptr, Util::StringToWstring(it->second).c_str());
}
return output;
}
static Json::Value* ShowHelp(std::vector<std::string>& args)
{
JDI::Hub.Help();
Report(Channel::Header, "## Debugger F-Keys\n");
Report(Channel::Norm, "- F1: Registers (left/right arrows to select registers)\n");
Report(Channel::Norm, "- F2: Memory dump\n");
Report(Channel::Norm, "- F3: Instruction disassembly\n");
Report(Channel::Norm, "- F5: Start emulation to breakpoint/pause emulation (break)\n");
Report(Channel::Norm, "- F9: Toogle instruction breakpoint\n");
Report(Channel::Norm, "- F10: Step over\n");
Report(Channel::Norm, "- F11: Step into\n");
Report(Channel::Norm, "- F12: Skip instruction\n");
return nullptr;
}
static Json::Value* IsCommandExists(std::vector<std::string>& args)
{
Json::Value* output = new Json::Value();
output->type = Json::ValueType::Bool;
output->value.AsBool = JDI::Hub.CommandExists(args[1]);
return output;
}
// Get the value of the debug counter
static Json::Value* CmdGetPerformanceCounter(std::vector<std::string>& args)
{
PerfCounter counter = (PerfCounter)strtoul(args[1].c_str(), nullptr, 0);
Json::Value* output = new Json::Value();
output->type = Json::ValueType::Int;
output->value.AsInt = g_PerfCounters->GetCounter(counter);
return output;
}
// Reset the value of the debug counter
static Json::Value* CmdResetPerformanceCounter(std::vector<std::string>& args)
{
PerfCounter counter = (PerfCounter)strtoul(args[1].c_str(), nullptr, 0);
g_PerfCounters->ResetCounter(counter);
return nullptr;
}
void Reflector()
{
JDI::Hub.AddCmd("script", cmd_script);
JDI::Hub.AddCmd("echo", cmd_echo);
JDI::Hub.AddCmd("StartProfiler", StartProfiler);
JDI::Hub.AddCmd("StopProfiler", StopProfiler);
JDI::Hub.AddCmd("GetChannelName", GetChannelName);
JDI::Hub.AddCmd("qd", QueryDebugMessages);
JDI::Hub.AddCmd("help", ShowHelp);
JDI::Hub.AddCmd("IsCommandExists", IsCommandExists);
JDI::Hub.AddCmd("GetPerformanceCounter", CmdGetPerformanceCounter);
JDI::Hub.AddCmd("ResetPerformanceCounter", CmdResetPerformanceCounter);
}
}
// Event log support
namespace Debug
{
EventLog* Log;
EventLog::EventLog()
{
traceEvents = eventHistory.root.AddArray(nullptr);
assert(traceEvents);
}
EventLog::~EventLog()
{
// The memory will be cleared along with json root (eventHistory).
}
void EventLog::TraceBegin(Channel chan, char* s)
{
eventLock.Lock();
Json::Value* entry = traceEvents->AddObject(nullptr);
assert(entry);
Json::Value* pid = entry->AddInt("pid", 1);
assert(pid);
Json::Value* tid = entry->AddInt("pid", (int)chan);
assert(tid);
Json::Value* ts = entry->AddUInt64("ts", Core->GetTicks());
assert(ts);
Json::Value* ph = entry->AddAnsiString("ph", "B");
assert(ph);
Json::Value* name = entry->AddAnsiString("name", s);
assert(name);
eventLock.Unlock();
}
void EventLog::TraceEnd(Channel chan)
{
eventLock.Lock();
Json::Value* entry = traceEvents->AddObject(nullptr);
assert(entry);
Json::Value* pid = entry->AddInt("pid", 1);
assert(pid);
Json::Value* tid = entry->AddInt("pid", (int)chan);
assert(tid);
Json::Value* ts = entry->AddUInt64("ts", Core->GetTicks());
assert(ts);
Json::Value* ph = entry->AddAnsiString("ph", "E");
assert(ph);
eventLock.Unlock();
}
void EventLog::TraceEvent(Channel chan, char* text)
{
eventLock.Lock();
Json::Value* entry = traceEvents->AddObject(nullptr);
assert(entry);
Json::Value* pid = entry->AddInt("pid", 1);
assert(pid);
Json::Value* tid = entry->AddInt("pid", (int)chan);
assert(tid);
Json::Value* ts = entry->AddUInt64("ts", Core->GetTicks());
assert(ts);
Json::Value* ph = entry->AddAnsiString("ph", "I");
assert(ph);
Json::Value* name = entry->AddAnsiString("name", text);
assert(name);
eventLock.Unlock();
}
void EventLog::ToString(std::string& jsonText)
{
size_t actualTextSize = 0;
eventHistory.GetSerializedTextSize((void*)jsonText.data(), -1, actualTextSize);
jsonText.resize(actualTextSize);
eventHistory.Serialize((void*)jsonText.data(), actualTextSize, actualTextSize);
}
}
namespace Debug
{
PerfCounters* g_PerfCounters = nullptr;
PerfCounters::PerfCounters()
{
}
PerfCounters::~PerfCounters()
{
}
int64_t PerfCounters::GetCounter(PerfCounter counter)
{
int64_t value = 0;
switch (counter)
{
case PerfCounter::GekkoInstructions:
return Core->GetInstructionCounter();
break;
case PerfCounter::DspInstructions:
return Flipper::DSP->core->GetInstructionCounter();
break;
case PerfCounter::VIs:
return pi.intCounters[(size_t)PIInterruptSource::VI];
break;
case PerfCounter::PEs:
return pi.intCounters[(size_t)PIInterruptSource::PE_FINISH];
break;
}
return value;
}
void PerfCounters::ResetCounter(PerfCounter counter)
{
switch (counter)
{
case PerfCounter::GekkoInstructions:
Core->ResetInstructionCounter();
break;
case PerfCounter::DspInstructions:
Flipper::DSP->core->ResetInstructionCounter();
break;
case PerfCounter::VIs:
pi.intCounters[(size_t)PIInterruptSource::VI] = 0;
break;
case PerfCounter::PEs:
pi.intCounters[(size_t)PIInterruptSource::PE_FINISH] = 0;
break;
}
}
void PerfCounters::ResetAllCounters()
{
for (int i = 0; i < (int)PerfCounter::Max; i++)
{
ResetCounter((PerfCounter)i);
}
}
}