Mesen2/Core/Debugger/BaseTraceLogger.h

509 lines
14 KiB
C++

#pragma once
#include "pch.h"
#include <regex>
#include "Utilities/SimpleLock.h"
#include "Debugger/DisassemblyInfo.h"
#include "Debugger/Debugger.h"
#include "Debugger/IDebugger.h"
#include "Debugger/DebugTypes.h"
#include "Debugger/LabelManager.h"
#include "Debugger/DebugTypes.h"
#include "Debugger/DebugUtilities.h"
#include "Debugger/DebugBreakHelper.h"
#include "Debugger/ITraceLogger.h"
#include "Debugger/ExpressionEvaluator.h"
#include "Debugger/TraceLogFileSaver.h"
#include "Utilities/HexUtilities.h"
#include "Shared/Emulator.h"
#include "Shared/EmuSettings.h"
class IConsole;
class Debugger;
class LabelManager;
class MemoryDumper;
class EmuSettings;
enum class RowDataType
{
Text = 0,
ByteCode,
Disassembly,
EffectiveAddress,
MemoryValue,
Align,
PC,
A,
B,
C,
D,
E,
F,
H,
K,
L,
M,
N,
X,
Y,
DB,
SP,
PS,
Cycle,
Scanline,
HClock,
FrameCount,
CycleCount,
R0,
R1,
R2,
R3,
R4,
R5,
R6,
R7,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
Src,
Dst,
SFR,
MAR,
MDR,
DPR,
ML,
MH,
PB,
P,
RP,
DP,
DR,
SR,
TR,
TRB,
FlagsA,
FlagsB
};
struct TraceLogPpuState
{
uint32_t Cycle;
uint32_t HClock;
int32_t Scanline;
uint32_t FrameCount;
};
struct RowPart
{
RowDataType DataType;
string Text;
bool DisplayInHex;
int MinWidth;
};
template<typename TraceLoggerType, typename CpuStateType>
class BaseTraceLogger : public ITraceLogger
{
protected:
static constexpr int ExecutionLogSize = 30000;
TraceLoggerOptions _options;
IConsole* _console;
EmuSettings* _settings;
LabelManager* _labelManager;
MemoryDumper* _memoryDumper;
Debugger* _debugger;
CpuType _cpuType = CpuType::Snes;
MemoryType _cpuMemoryType = MemoryType::SnesMemory;
vector<RowPart> _rowParts;
uint32_t _currentPos = 0;
bool _pendingLog = false;
CpuStateType _lastState = {};
DisassemblyInfo _lastDisassemblyInfo = {};
CpuStateType* _cpuState = nullptr;
DisassemblyInfo *_disassemblyCache = nullptr;
uint64_t* _rowIds = nullptr;
TraceLogPpuState* _ppuState = nullptr;
unique_ptr<ExpressionEvaluator> _expEvaluator;
ExpressionData _conditionData;
void WriteByteCode(DisassemblyInfo& info, RowPart& rowPart, string& output)
{
string byteCode;
info.GetByteCode(byteCode);
if(!rowPart.DisplayInHex) {
//Remove $ marks if not in "hex" mode (but still display the bytes as hex)
byteCode.erase(std::remove(byteCode.begin(), byteCode.end(), '$'), byteCode.end());
}
WriteStringValue(output, byteCode, rowPart);
}
void WriteDisassembly(DisassemblyInfo& info, RowPart& rowPart, uint8_t sp, uint32_t pc, string& output)
{
int indentLevel = 0;
size_t startPos = output.size();
if(_options.IndentCode) {
indentLevel = 0xFF - (sp & 0xFF);
output += std::string(indentLevel / 2, ' ');
}
LabelManager* labelManager = _options.UseLabels ? _labelManager : nullptr;
info.GetDisassembly(output, pc, labelManager, _settings);
if(rowPart.MinWidth > (int)(output.size() - startPos)) {
output += std::string(rowPart.MinWidth - (output.size() - startPos), ' ');
}
}
void WriteEffectiveAddress(DisassemblyInfo& info, RowPart& rowPart, void* cpuState, string& output, MemoryType cpuMemoryType, CpuType cpuType)
{
EffectiveAddressInfo effectiveAddress = info.GetEffectiveAddress(_debugger, cpuState, cpuType);
if(effectiveAddress.ShowAddress && effectiveAddress.Address >= 0) {
if(_options.UseLabels) {
AddressInfo addr { effectiveAddress.Address, cpuMemoryType };
string label = _labelManager->GetLabel(addr);
if(!label.empty()) {
WriteStringValue(output, " [" + label + "]", rowPart);
return;
}
}
WriteStringValue(output, " [$" + DebugUtilities::AddressToHex(cpuType, effectiveAddress.Address) + "]", rowPart);
}
}
void WriteMemoryValue(DisassemblyInfo& info, RowPart& rowPart, void* cpuState, string& output, MemoryType memType, CpuType cpuType)
{
EffectiveAddressInfo effectiveAddress = info.GetEffectiveAddress(_debugger, cpuState, cpuType);
if(effectiveAddress.Address >= 0 && effectiveAddress.ValueSize > 0) {
uint16_t value = info.GetMemoryValue(effectiveAddress, _memoryDumper, memType);
if(rowPart.DisplayInHex) {
output += "= $";
if(effectiveAddress.ValueSize == 2) {
WriteIntValue(output, (uint16_t)value, rowPart);
} else {
WriteIntValue(output, (uint8_t)value, rowPart);
}
} else {
output += "= ";
}
}
}
void GetStatusFlag(const char* activeStatusLetters, const char* inactiveStatusLetters, string& output, uint32_t ps, RowPart& part, int length = 8)
{
if(part.DisplayInHex) {
WriteIntValue(output, ps, part);
} else {
string flags;
for(int i = 0; i < length; i++) {
if(ps & (1 << (length - 1))) {
flags += activeStatusLetters[i];
} else if(part.MinWidth >= length) {
flags += inactiveStatusLetters[i];
}
ps <<= 1;
}
WriteStringValue(output, flags, part);
}
}
void WriteAlign(int originalSize, RowPart& rowPart, string& output)
{
if((int)output.size() - originalSize < rowPart.MinWidth) {
output.append(rowPart.MinWidth - (output.size() - originalSize), ' ');
}
}
template<typename T>
void WriteIntValue(string& output, T value, RowPart& rowPart)
{
string str = rowPart.DisplayInHex ? HexUtilities::ToHex(value) : std::to_string(value);
if(rowPart.MinWidth > (int)str.size()) {
if(rowPart.DisplayInHex) {
str = std::string(rowPart.MinWidth - str.size(), '0') + str;
} else {
str += std::string(rowPart.MinWidth - str.size(), ' ');
}
}
output += str;
}
void WriteStringValue(string& output, string value, RowPart& rowPart)
{
output += value;
if(rowPart.MinWidth > (int)value.size()) {
output += std::string(rowPart.MinWidth - value.size(), ' ');
}
}
void AddRow(CpuStateType& cpuState, DisassemblyInfo& disassemblyInfo)
{
_disassemblyCache[_currentPos] = disassemblyInfo;
_cpuState[_currentPos] = cpuState;
((TraceLoggerType*)this)->LogPpuState();
_rowIds[_currentPos] = ITraceLogger::NextRowId;
ITraceLogger::NextRowId++;
_pendingLog = false;
if(_debugger->GetTraceLogFileSaver()->IsEnabled()) {
string row;
row.reserve(300);
//Display PC
RowPart rowPart = {};
rowPart.DisplayInHex = true;
rowPart.MinWidth = DebugUtilities::GetProgramCounterSize(_cpuType);
WriteIntValue(row, ((TraceLoggerType*)this)->GetProgramCounter(cpuState), rowPart);
row += " ";
((TraceLoggerType*)this)->GetTraceRow(row, cpuState, _ppuState[_currentPos], disassemblyInfo);
_debugger->GetTraceLogFileSaver()->Log(row);
}
_currentPos = (_currentPos + 1) % ExecutionLogSize;
}
void ParseFormatString(string format)
{
_rowParts.clear();
std::regex formatRegex = std::regex("(\\[\\s*([^[]*?)\\s*(,\\s*([\\d]*)\\s*(h){0,1}){0,1}\\s*\\])|([^[]*)", std::regex_constants::icase);
std::sregex_iterator start = std::sregex_iterator(format.cbegin(), format.cend(), formatRegex);
std::sregex_iterator end = std::sregex_iterator();
for(std::sregex_iterator it = start; it != end; it++) {
const std::smatch& match = *it;
if(match.str(1) == "") {
RowPart part = {};
part.DataType = RowDataType::Text;
part.Text = match.str(6);
_rowParts.push_back(part);
} else {
RowPart part = {};
string tag = match.str(2);
part.DataType = InternalGetFormatTagType(tag);
if(part.DataType == RowDataType::Text) {
part.Text = "[Invalid tag]";
}
if(!match.str(4).empty()) {
try {
part.MinWidth = std::stoi(match.str(4));
} catch(std::exception&) {
}
}
part.DisplayInHex = match.str(5) == "h";
_rowParts.push_back(part);
}
}
}
RowDataType InternalGetFormatTagType(string& tag)
{
if(tag == "ByteCode") {
return RowDataType::ByteCode;
} else if(tag == "Disassembly") {
return RowDataType::Disassembly;
} else if(tag == "EffectiveAddress") {
return RowDataType::EffectiveAddress;
} else if(tag == "MemoryValue") {
return RowDataType::MemoryValue;
} else if(tag == "Align") {
return RowDataType::Align;
} else if(tag == "PC") {
return RowDataType::PC;
} else if(tag == "Cycle") {
return RowDataType::Cycle;
} else if(tag == "HClock") {
return RowDataType::HClock;
} else if(tag == "Scanline") {
return RowDataType::Scanline;
} else if(tag == "FrameCount") {
return RowDataType::FrameCount;
} else if(tag == "CycleCount") {
return RowDataType::CycleCount;
}
return GetFormatTagType(tag);
}
virtual RowDataType GetFormatTagType(string& tag) = 0;
void ProcessSharedTag(RowPart& rowPart, string& output, CpuStateType& cpuState, TraceLogPpuState& ppuState, DisassemblyInfo& disassemblyInfo)
{
switch(rowPart.DataType) {
case RowDataType::Text: output += rowPart.Text; break;
case RowDataType::ByteCode: WriteByteCode(disassemblyInfo, rowPart, output); break;
case RowDataType::Disassembly: WriteDisassembly(disassemblyInfo, rowPart, ((TraceLoggerType*)this)->GetStackPointer(cpuState), ((TraceLoggerType*)this)->GetProgramCounter(cpuState), output); break;
case RowDataType::EffectiveAddress: WriteEffectiveAddress(disassemblyInfo, rowPart, &cpuState, output, _cpuMemoryType, _cpuType); break;
case RowDataType::MemoryValue: WriteMemoryValue(disassemblyInfo, rowPart, &cpuState, output, _cpuMemoryType, _cpuType); break;
case RowDataType::Align: WriteAlign(0, rowPart, output); break;
case RowDataType::Cycle: WriteIntValue(output, ppuState.Cycle, rowPart); break;
case RowDataType::Scanline: WriteIntValue(output, ppuState.Scanline, rowPart); break;
case RowDataType::HClock: WriteIntValue(output, ppuState.HClock, rowPart); break;
case RowDataType::FrameCount: WriteIntValue(output, ppuState.FrameCount, rowPart); break;
case RowDataType::CycleCount: WriteIntValue(output, (uint64_t)((TraceLoggerType*)this)->GetCycleCount(cpuState), rowPart); break;
case RowDataType::PC: WriteStringValue(output, HexUtilities::ToHex(((TraceLoggerType*)this)->GetProgramCounter(cpuState)), rowPart); break;
}
}
public:
BaseTraceLogger(Debugger* debugger, IDebugger* cpuDebugger, CpuType cpuType)
{
_debugger = debugger;
_console = debugger->GetConsole();
_settings = debugger->GetEmulator()->GetSettings();
_labelManager = debugger->GetLabelManager();
_memoryDumper = debugger->GetMemoryDumper();
_options = {};
_currentPos = 0;
_pendingLog = false;
_disassemblyCache = new DisassemblyInfo[BaseTraceLogger::ExecutionLogSize];
_rowIds = new uint64_t[BaseTraceLogger::ExecutionLogSize];
memset(_disassemblyCache, 0, sizeof(DisassemblyInfo) * BaseTraceLogger::ExecutionLogSize);
memset(_rowIds, 0, sizeof(uint64_t) * BaseTraceLogger::ExecutionLogSize);
_ppuState = new TraceLogPpuState[BaseTraceLogger::ExecutionLogSize];
memset(_ppuState, 0, sizeof(TraceLogPpuState) * BaseTraceLogger::ExecutionLogSize);
_cpuState = new CpuStateType[BaseTraceLogger::ExecutionLogSize];
memset(_cpuState, 0, sizeof(CpuStateType) * BaseTraceLogger::ExecutionLogSize);
_cpuType = cpuType;
_cpuMemoryType = DebugUtilities::GetCpuMemoryType(cpuType);
_expEvaluator.reset(new ExpressionEvaluator(debugger, cpuDebugger, cpuType));
}
virtual ~BaseTraceLogger()
{
delete[] _disassemblyCache;
delete[] _rowIds;
delete[] _ppuState;
delete[] _cpuState;
}
void Clear() override
{
_currentPos = 0;
memset(_rowIds, 0, sizeof(uint64_t) * BaseTraceLogger::ExecutionLogSize);
}
void LogNonExec(MemoryOperationInfo& operation, AddressInfo& addressInfo)
{
if(_pendingLog) {
int pos = _currentPos - 1;
if(pos < 0) {
pos = BaseTraceLogger::ExecutionLogSize - 1;
}
if(ConditionMatches(_lastDisassemblyInfo, operation, addressInfo)) {
AddRow(_lastState, _lastDisassemblyInfo);
_pendingLog = false;
}
}
}
void Log(CpuStateType& cpuState, DisassemblyInfo& disassemblyInfo, MemoryOperationInfo& operation, AddressInfo& addressInfo)
{
if(_enabled) {
//For the sake of performance, only log data for the CPUs we're actively displaying/logging
if(ConditionMatches(disassemblyInfo, operation, addressInfo)) {
AddRow(cpuState, disassemblyInfo);
} else {
_pendingLog = true;
_lastState = cpuState;
_lastDisassemblyInfo = disassemblyInfo;
}
}
}
void SetOptions(TraceLoggerOptions options) override
{
DebugBreakHelper helper(_debugger);
_options = options;
_enabled = options.Enabled;
string condition = _options.Condition;
string format = _options.Format;
_conditionData = ExpressionData();
if(!condition.empty()) {
bool success = false;
ExpressionData rpnList = _expEvaluator->GetRpnList(condition, success);
if(success) {
_conditionData = rpnList;
}
}
ParseFormatString(format);
_debugger->ProcessConfigChange();
}
int64_t GetRowId(uint32_t offset) override
{
int32_t pos = ((int32_t)_currentPos - (int32_t)offset);
int32_t i = (pos > 0 ? pos : BaseTraceLogger::ExecutionLogSize + pos) - 1;
if(!_disassemblyCache[i].IsInitialized()) {
return -1;
}
return _rowIds[i];
}
bool ConditionMatches(DisassemblyInfo &disassemblyInfo, MemoryOperationInfo &operationInfo, AddressInfo& addressInfo)
{
if(!_conditionData.RpnQueue.empty()) {
EvalResultType type;
if(!_expEvaluator->Evaluate(_conditionData, type, operationInfo, addressInfo)) {
return false;
}
}
return true;
}
void GetExecutionTrace(TraceRow& row, uint32_t offset) override
{
int pos = ((int)_currentPos - offset);
int index = (pos > 0 ? pos : BaseTraceLogger::ExecutionLogSize + pos) - 1;
CpuStateType& state = _cpuState[index];
string logOutput;
logOutput.reserve(300);
((TraceLoggerType*)this)->GetTraceRow(logOutput, state, _ppuState[index], _disassemblyCache[index]);
row.Type = _cpuType;
_disassemblyCache[index].GetByteCode(row.ByteCode);
row.ByteCodeSize = _disassemblyCache[index].GetOpSize();
row.ProgramCounter = ((TraceLoggerType*)this)->GetProgramCounter(state);
row.LogSize = std::min<uint32_t>(499, (uint32_t)logOutput.size());
memcpy(row.LogOutput, logOutput.c_str(), row.LogSize);
row.LogOutput[row.LogSize] = 0;
}
};