mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge pull request #22072 from hrydgard/improve-headless-output
Improve crash message formatting, headless output
This commit is contained in:
33 files changed
+370
-184
No files matched your search
@@ -60,6 +60,7 @@ static const char level_to_char[8] = "-NEWIDV";
|
||||
void AndroidLog(const LogMessage &message);
|
||||
#endif
|
||||
|
||||
// TODO: Get rid of this wrapper, not much point.
|
||||
void GenericLog(Log type, LogLevel level, const char *file, int line, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
@@ -114,7 +114,7 @@ void ServerRequest::WriteHttpResponseHeader(const char *ver, int status, int64_t
|
||||
buffer->Push("Connection: close\r\n");
|
||||
}
|
||||
if (size >= 0) {
|
||||
buffer->Printf("Content-Length: %llu\r\n", size);
|
||||
buffer->Printf("Content-Length: %llu\r\n", (unsigned long long)size);
|
||||
}
|
||||
if (otherHeaders) {
|
||||
buffer->Push(otherHeaders, strlen(otherHeaders));
|
||||
|
||||
@@ -207,5 +207,3 @@ void System_RunCallbackInWndProc(void (*callback)(void *, void *), void *userdat
|
||||
// Non-inline to avoid including Path.h
|
||||
void System_CreateGameShortcut(const Path &path, std::string_view title);
|
||||
void System_ShowFileInFolder(const Path &path);
|
||||
bool System_SendDebugOutput(std::string_view string);
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height);
|
||||
+39
-13
@@ -28,11 +28,12 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
u8 codeByte2 = 0;
|
||||
|
||||
//Check for regular prefix
|
||||
info.operandSize = 4;
|
||||
info.operandSizeInBytes = 4;
|
||||
info.zeroExtend = false;
|
||||
info.signExtend = false;
|
||||
info.hasImmediate = false;
|
||||
info.isMemoryWrite = false;
|
||||
info.instructionClass = InstructionClass::GPR;
|
||||
|
||||
int addressSize = 8;
|
||||
u8 modRMbyte = 0;
|
||||
@@ -40,19 +41,26 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
bool hasModRM = false;
|
||||
bool hasSIBbyte = false;
|
||||
bool hasDisplacement = false;
|
||||
bool hasF3Prefix = false;
|
||||
|
||||
int displacementSize = 0;
|
||||
|
||||
if (*codePtr == 0x66)
|
||||
{
|
||||
info.operandSize = 2;
|
||||
info.operandSizeInBytes = 2;
|
||||
codePtr++;
|
||||
}
|
||||
}
|
||||
else if (*codePtr == 0x67)
|
||||
{
|
||||
addressSize = 4;
|
||||
codePtr++;
|
||||
}
|
||||
else if (*codePtr == 0xF3)
|
||||
{
|
||||
// Mandatory prefix, distinguishes MOVSS (scalar) from MOVUPS (full xmm) on the same opcode.
|
||||
hasF3Prefix = true;
|
||||
codePtr++;
|
||||
}
|
||||
|
||||
//Check for REX prefix
|
||||
if ((*codePtr & 0xF0) == 0x40)
|
||||
@@ -60,7 +68,7 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
rex = *codePtr;
|
||||
if (rex & 8) //REX.W
|
||||
{
|
||||
info.operandSize = 8;
|
||||
info.operandSizeInBytes = 8;
|
||||
}
|
||||
codePtr++;
|
||||
}
|
||||
@@ -159,19 +167,19 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
|
||||
case MOVE_16_32BIT: //move 16 or 32-bit immediate, easiest case for writes
|
||||
{
|
||||
if (info.operandSize == 2)
|
||||
if (info.operandSizeInBytes == 2)
|
||||
{
|
||||
info.hasImmediate = true;
|
||||
info.immediate = *(u16*)codePtr;
|
||||
codePtr += 2;
|
||||
}
|
||||
else if (info.operandSize == 4)
|
||||
else if (info.operandSizeInBytes == 4)
|
||||
{
|
||||
info.hasImmediate = true;
|
||||
info.immediate = *(u32*)codePtr;
|
||||
codePtr += 4;
|
||||
}
|
||||
else if (info.operandSize == 8)
|
||||
else if (info.operandSizeInBytes == 8)
|
||||
{
|
||||
info.zeroExtend = true;
|
||||
info.immediate = *(u32*)codePtr;
|
||||
@@ -205,28 +213,46 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
{
|
||||
case MOVZX_BYTE: //movzx on byte
|
||||
info.zeroExtend = true;
|
||||
info.operandSize = 1;
|
||||
info.operandSizeInBytes = 1;
|
||||
break;
|
||||
case MOVZX_SHORT: //movzx on short
|
||||
info.zeroExtend = true;
|
||||
info.operandSize = 2;
|
||||
info.operandSizeInBytes = 2;
|
||||
break;
|
||||
case MOVSX_BYTE: //movsx on byte
|
||||
info.signExtend = true;
|
||||
info.operandSize = 1;
|
||||
info.operandSizeInBytes = 1;
|
||||
break;
|
||||
case MOVSX_SHORT: //movsx on short
|
||||
info.signExtend = true;
|
||||
info.operandSize = 2;
|
||||
info.operandSizeInBytes = 2;
|
||||
break;
|
||||
case MOVUPS_MOVSS_FROM_RM: //movups/movss xmm, xmm/m (load)
|
||||
info.instructionClass = hasF3Prefix ? InstructionClass::FP : InstructionClass::FP_SIMD;
|
||||
info.operandSizeInBytes = hasF3Prefix ? 4 : 16;
|
||||
break;
|
||||
case MOVUPS_MOVSS_TO_RM: //movups/movss xmm/m, xmm (store)
|
||||
info.instructionClass = hasF3Prefix ? InstructionClass::FP : InstructionClass::FP_SIMD;
|
||||
info.operandSizeInBytes = hasF3Prefix ? 4 : 16;
|
||||
info.isMemoryWrite = true;
|
||||
break;
|
||||
case MOVAPS_FROM_RM: //movaps xmm, xmm/m (load)
|
||||
info.instructionClass = InstructionClass::FP_SIMD;
|
||||
info.operandSizeInBytes = 16;
|
||||
break;
|
||||
case MOVAPS_TO_RM: //movaps xmm/m, xmm (store)
|
||||
info.instructionClass = InstructionClass::FP_SIMD;
|
||||
info.operandSizeInBytes = 16;
|
||||
info.isMemoryWrite = true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 0x8a:
|
||||
if (info.operandSize == 4)
|
||||
if (info.operandSizeInBytes == 4)
|
||||
{
|
||||
info.operandSize = 1;
|
||||
info.operandSizeInBytes = 1;
|
||||
break;
|
||||
}
|
||||
else
|
||||
|
||||
+18
-3
@@ -19,9 +19,16 @@
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
struct LSInstructionInfo
|
||||
{
|
||||
int operandSize; //8, 16, 32, 64
|
||||
// What kind of register regOperandReg (and otherReg, if used as a source/dest rather than
|
||||
// just an address component) refers to, and thus how the instruction should be interpreted.
|
||||
enum class InstructionClass {
|
||||
GPR, // General-purpose register (mov, movzx, movsx, ...)
|
||||
FP, // Scalar floating point (movss, movsd, ...)
|
||||
FP_SIMD, // Full vector register (movups, movaps, movdqa, ...)
|
||||
};
|
||||
|
||||
struct LSInstructionInfo {
|
||||
int operandSizeInBytes; // 1, 2, 4, 8 (in bytes, despite the field name suggesting bits)
|
||||
int instructionSize;
|
||||
int regOperandReg;
|
||||
int otherReg;
|
||||
@@ -32,6 +39,9 @@ struct LSInstructionInfo
|
||||
bool isMemoryWrite;
|
||||
u64 immediate;
|
||||
s32 displacement;
|
||||
InstructionClass instructionClass;
|
||||
|
||||
int OperandSizeInBytes() const { return operandSizeInBytes; }
|
||||
};
|
||||
|
||||
struct ModRM
|
||||
@@ -54,6 +64,11 @@ enum {
|
||||
MOVE_16_32BIT = 0xC7, //move 16 or 32-bit immediate
|
||||
MOVE_REG_TO_MEM = 0x89, //move reg to memory
|
||||
MOVE_MEM_TO_REG = 0x8B, //move memory to reg
|
||||
// These two opcodes are shared between MOVUPS (no mandatory prefix) and MOVSS (mandatory 0xF3 prefix).
|
||||
MOVUPS_MOVSS_FROM_RM = 0x10, //movups/movss xmm, xmm/m
|
||||
MOVUPS_MOVSS_TO_RM = 0x11, //movups/movss xmm/m, xmm
|
||||
MOVAPS_FROM_RM = 0x28, //movaps xmm, xmm/m
|
||||
MOVAPS_TO_RM = 0x29, //movaps xmm/m, xmm
|
||||
};
|
||||
|
||||
enum AccessType {
|
||||
|
||||
+31
-8
@@ -242,10 +242,11 @@ int CommandLineOptions::PrintUsage(const char *progname, const char *situationTe
|
||||
PRINT_STDOUT(" -v set the log level to verbose\n");
|
||||
PRINT_STDOUT(" --loglevel=INTEGER set the log level to specified value\n");
|
||||
if (mode == CmdLineMode::Application) {
|
||||
PRINT_STDOUT(" --log=FILE output log to FILE\n");
|
||||
PRINT_STDOUT(" --log=FILE output log to FILE\n");
|
||||
}
|
||||
PRINT_STDOUT(" --state=FILE load state from FILE\n");
|
||||
|
||||
PRINT_STDOUT(" --cpu=CPU use the specified CPU core (interpreter, ir, jit, jit-ir)\n");
|
||||
PRINT_STDOUT(" -i use the interpreter\n");
|
||||
PRINT_STDOUT(" -r use IR interpreter\n");
|
||||
PRINT_STDOUT(" -j use JIT\n");
|
||||
@@ -290,6 +291,7 @@ int CommandLineOptions::PrintUsage(const char *progname, const char *situationTe
|
||||
// Actually might want to reconsider given Android...
|
||||
CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], CmdLineMode mode) {
|
||||
this->mode = mode;
|
||||
constexpr std::string_view cpuBackendStr = "--cpu=";
|
||||
constexpr std::string_view gpuBackendStr = "--graphics=";
|
||||
constexpr std::string_view configOption = "--config=";
|
||||
constexpr std::string_view controlsOption = "--controlconfig=";
|
||||
@@ -413,7 +415,27 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C
|
||||
} else {
|
||||
// Bad value, report error and exit.
|
||||
PRINT_STDERR("Invalid value for --graphics=: %s", restOfOption.c_str());
|
||||
return CommandLineParseResult::Exit;
|
||||
return CommandLineParseResult::Error;
|
||||
}
|
||||
} else if (startsWith(argv[i], cpuBackendStr)) {
|
||||
const std::string restOfOption = argv[i] + cpuBackendStr.size();
|
||||
// Force software rendering off, as picking gles implies HW acceleration.
|
||||
// We could add more options for software such as "software-gles",
|
||||
// "software-vulkan" and "software-d3d11", or something similar.
|
||||
// For now, software rendering force-activates OpenGL.
|
||||
double glVersionTemp = 0.0f;
|
||||
if (restOfOption == "interpreter") {
|
||||
cpuCore = CPUCore::INTERPRETER;
|
||||
} else if (restOfOption == "jit") {
|
||||
cpuCore = CPUCore::JIT;
|
||||
} else if (restOfOption == "jit-ir") {
|
||||
cpuCore = CPUCore::JIT_IR;
|
||||
} else if (restOfOption == "ir") {
|
||||
cpuCore = CPUCore::IR_INTERPRETER;
|
||||
} else {
|
||||
// Bad value, report error and exit.
|
||||
PRINT_STDERR("Invalid value for --cpu=: %s", restOfOption.c_str());
|
||||
return CommandLineParseResult::Error;
|
||||
}
|
||||
} else if (startsWith(argv[i], configOption)) {
|
||||
configFilename = std::string(argv[i] + configOption.size());
|
||||
@@ -429,10 +451,11 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C
|
||||
continue;
|
||||
} else {
|
||||
PRINT_STDERR("Error: --ignore requires an argument.\n");
|
||||
return CommandLineParseResult::Exit;
|
||||
return CommandLineParseResult::Error;
|
||||
}
|
||||
} else {
|
||||
// Report unknown argument later once this is complete.
|
||||
} else if (startsWith(argv[i], "--")) {
|
||||
PRINT_STDERR("Error: Unknown parameter: %s\n", argv[i]);
|
||||
return CommandLineParseResult::Error;
|
||||
}
|
||||
// To the next argument.
|
||||
i++;
|
||||
@@ -456,6 +479,9 @@ void CommandLineOptions::ApplyToConfig() const {
|
||||
g_Config.iGPUBackend = (int)gpuBackend.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.iGPUBackend);
|
||||
}
|
||||
if (cpuCore.has_value()) {
|
||||
g_Config.iCpuCore = (int)cpuCore.value();
|
||||
}
|
||||
if (softwareRendering.has_value()) {
|
||||
g_Config.bSoftwareRendering = softwareRendering.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.bSoftwareRendering);
|
||||
@@ -468,9 +494,6 @@ void CommandLineOptions::ApplyToConfig() const {
|
||||
g_Config.bAutoRun = false;
|
||||
g_Config.bSaveSettings = false;
|
||||
}
|
||||
if (cpuCore.has_value()) {
|
||||
g_Config.iCpuCore = (int)cpuCore.value();
|
||||
}
|
||||
if (escapeExit.has_value()) {
|
||||
g_Config.bPauseExitsEmulator = escapeExit.value();
|
||||
}
|
||||
|
||||
+61
-17
@@ -32,6 +32,7 @@
|
||||
#include "Common/GPU/GraphicsContext.h"
|
||||
#include "Common/Thread/ThreadUtil.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
@@ -155,6 +156,28 @@ static MIPSExceptionInfo g_exceptionInfo;
|
||||
// This is called on EmuThread before RunLoop.
|
||||
static bool Core_ProcessStepping(MIPSDebugInterface *cpu);
|
||||
|
||||
static std::function<void(std::string_view)> g_debugOutputListener;
|
||||
static std::function<void(const DebugScreenshotDesc &)> g_debugScreenshotListener;
|
||||
|
||||
void Core_RegisterDebugOutputListeners(std::function<void(std::string_view)> listener, std::function<void(const DebugScreenshotDesc &)> screenshotListener) {
|
||||
g_debugOutputListener = std::move(listener);
|
||||
g_debugScreenshotListener = std::move(screenshotListener);
|
||||
}
|
||||
|
||||
void Core_SendDebugOutput(LogLevel level, std::string_view string) {
|
||||
if (g_debugOutputListener) {
|
||||
g_debugOutputListener(string);
|
||||
} else {
|
||||
GENERIC_LOG(Log::sceIo, level, "%.*s", STR_VIEW(string));
|
||||
}
|
||||
}
|
||||
|
||||
void Core_SendDebugScreenshot(const DebugScreenshotDesc &desc) {
|
||||
if (g_debugScreenshotListener) {
|
||||
g_debugScreenshotListener(desc);
|
||||
}
|
||||
}
|
||||
|
||||
BreakReason Core_BreakReason() {
|
||||
return g_breakReason;
|
||||
}
|
||||
@@ -642,11 +665,25 @@ static std::string ModuleAddressSuffix(u32 address) {
|
||||
|
||||
void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo) {
|
||||
// In jit, we only flush PC when bIgnoreBadMemAccess is off.
|
||||
|
||||
char pcDetails[128];
|
||||
pcDetails[0] = 0;
|
||||
if ((CPUCore)g_Config.iCpuCore == CPUCore::INTERPRETER) {
|
||||
snprintf(pcDetails, sizeof(pcDetails), " PC %08x%s RA %08x%s", currentMIPS->pc, ModuleAddressSuffix(currentMIPS->pc).c_str(), currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str());
|
||||
switch ((CPUCore)g_Config.iCpuCore) {
|
||||
case CPUCore::INTERPRETER:
|
||||
snprintf(pcDetails, sizeof(pcDetails), "Interpreter: PC %08x%s RA %08x%s",
|
||||
currentMIPS->pc, ModuleAddressSuffix(currentMIPS->pc).c_str(),
|
||||
currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str());
|
||||
break;
|
||||
case CPUCore::JIT:
|
||||
snprintf(pcDetails, sizeof(pcDetails), "JIT: (PC approximate)=%08x%s", pc, ModuleAddressSuffix(pc).c_str());
|
||||
break;
|
||||
case CPUCore::JIT_IR:
|
||||
snprintf(pcDetails, sizeof(pcDetails), "JIT_IR: (PC approximate)=%08x%s", pc, ModuleAddressSuffix(pc).c_str());
|
||||
break;
|
||||
case CPUCore::IR_INTERPRETER:
|
||||
snprintf(pcDetails, sizeof(pcDetails), "IR_INTERPRETER: (PC approximate)=%08x%s", pc, ModuleAddressSuffix(pc).c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string addressSuffix = ModuleAddressSuffix(address);
|
||||
@@ -665,15 +702,15 @@ void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionTy
|
||||
}
|
||||
|
||||
const char *desc = MemoryExceptionTypeAsString(type);
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "%s: SIGSEGV at %08x%s (size: %d bytes) %s\nHost:%.*s", desc, address, addressSuffix.c_str(), accessSize, pcDetails, STR_VIEW(additionalInfo));
|
||||
if (action == ExceptionAction::Ignore) {
|
||||
// Simplest logging and continue.
|
||||
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x%s (size %08x) %s%.*s", desc, address, addressSuffix.c_str(), accessSize, pcDetails, (int)additionalInfo.length(), additionalInfo.data());
|
||||
Core_SendDebugOutput(LogLevel::LWARNING, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1));
|
||||
// Do the most detailed logging we can.
|
||||
ERROR_LOG(Log::MemMap, "%s: Invalid access at %08x%s (size %08x) %s%.*s\n%s", desc, address, addressSuffix.c_str(), accessSize, pcDetails, (int)additionalInfo.length(), additionalInfo.data(), stackTrace.c_str());
|
||||
Core_SendDebugOutput(LogLevel::LERROR, StringFromFormat("%sMIPS call stack:\n%s", msg, stackTrace.c_str()));
|
||||
if (action == ExceptionAction::Break) {
|
||||
MIPSExceptionInfo &e = g_exceptionInfo;
|
||||
e = {};
|
||||
@@ -706,7 +743,7 @@ void Core_MemoryExceptionHLE(MIPSState *mips, u32 address, u32 accessSize, Memor
|
||||
const HLEFunction *func = HLEGetFunctionBeingCalled();
|
||||
const char *funcName = func ? func->name : "unknown";
|
||||
|
||||
char args[512] = "";
|
||||
char args[256] = "";
|
||||
if (func) {
|
||||
HLEFormatLogArgs(mips, args, sizeof(args), func->argmask);
|
||||
}
|
||||
@@ -724,22 +761,23 @@ void Core_MemoryExceptionHLE(MIPSState *mips, u32 address, u32 accessSize, Memor
|
||||
}
|
||||
|
||||
const u32 pc = mips->pc;
|
||||
const char *desc = MemoryExceptionTypeAsString(type);
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Invalid access in %s(%s) %s at %08x%s (size %08x) PC %08x%s RA %08x%s",
|
||||
funcName, args,
|
||||
extra, address, ModuleAddressSuffix(address).c_str(), accessSize,
|
||||
snprintf(msg, sizeof(msg), "%s: Invalid access %s in %s(%s) at %08x%s (size %08x) PC %08x%s RA %08x%s",
|
||||
desc, extra, funcName, args,
|
||||
address, ModuleAddressSuffix(address).c_str(), accessSize,
|
||||
pc, ModuleAddressSuffix(pc).c_str(),
|
||||
mips->r[MIPS_REG_RA], ModuleAddressSuffix(mips->r[MIPS_REG_RA]).c_str());
|
||||
|
||||
const char *desc = MemoryExceptionTypeAsString(type);
|
||||
if (action == ExceptionAction::Ignore) {
|
||||
// Simplest logging and continue.
|
||||
WARN_LOG(Log::MemMap, "HLE %s: %s", MemoryExceptionTypeAsString(type), msg);
|
||||
Core_SendDebugOutput(LogLevel::LWARNING, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1));
|
||||
ERROR_LOG(Log::MemMap, "%s: %s\n%s", desc, msg, stackTrace.c_str());
|
||||
Core_SendDebugOutput(LogLevel::LERROR, StringFromFormat("%s\n%s", msg, stackTrace.c_str()));
|
||||
if (action == ExceptionAction::Break) {
|
||||
MIPSExceptionInfo &e = g_exceptionInfo;
|
||||
e = {};
|
||||
@@ -757,7 +795,10 @@ void Core_MemoryExceptionHLE(MIPSState *mips, u32 address, u32 accessSize, Memor
|
||||
// Can't be ignored, must break. Not sure we can get a meaningful stack trace here (since the PC is invalid).
|
||||
void Core_ExecException(u32 address, u32 pc, ExecExceptionType type) {
|
||||
const char *desc = ExecExceptionTypeAsString(type);
|
||||
WARN_LOG(Log::MemMap, "%s: Invalid exec address %08x%s pc=%08x%s ra=%08x%s", desc, address, ModuleAddressSuffix(address).c_str(), pc, ModuleAddressSuffix(pc).c_str(), currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str());
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "%s: Invalid exec address %08x%s pc=%08x%s ra=%08x%s", desc, address, ModuleAddressSuffix(address).c_str(), pc, ModuleAddressSuffix(pc).c_str(), currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str());
|
||||
Core_SendDebugOutput(LogLevel::LERROR, msg);
|
||||
|
||||
MIPSExceptionInfo &e = g_exceptionInfo;
|
||||
e = {};
|
||||
@@ -781,15 +822,18 @@ void Core_BreakException(u32 pc) {
|
||||
|
||||
const std::string pcSuffix = ModuleAddressSuffix(pc);
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "CPU exception: break instruction hit at %08x%s. Ignoring (use --break=log for more details or --break=break to break)", pc, pcSuffix.c_str());
|
||||
|
||||
const ExceptionAction action = ResolveExceptionAction((ExceptionAction)g_Config.iExceptionActionBreak);
|
||||
if (action == ExceptionAction::Ignore) {
|
||||
// Simplest logging and continue.
|
||||
WARN_LOG(Log::CPU, "CPU exception: break instruction hit at %08x%s. Ignoring (use --break=log for more details or --break=break to break)", pc, pcSuffix.c_str());
|
||||
Core_SendDebugOutput(LogLevel::LINFO, StringFromFormat("Ignoring CPU exception: break instruction hit at %08x%s", pc, pcSuffix.c_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1));
|
||||
ERROR_LOG(Log::CPU, "CPU exception: break instruction hit at %08x%s (ra=%08x%s)\n%s", pc, pcSuffix.c_str(), currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str(), stackTrace.c_str());
|
||||
Core_SendDebugOutput(LogLevel::LERROR, StringFromFormat("%s\n%s", msg, stackTrace.c_str()));
|
||||
if (action == ExceptionAction::Break) {
|
||||
Core_Break(BreakReason::BreakInstruction, currentMIPS->pc);
|
||||
}
|
||||
|
||||
+15
@@ -215,6 +215,21 @@ void Core_BreakException(u32 pc);
|
||||
// Call when loading save states, etc.
|
||||
void Core_ResetException();
|
||||
|
||||
// Used by headless/pspautotest to collect data for the diffs. Crash reports are also sent here.
|
||||
// Log level is only used if the listener is not registered.
|
||||
enum class LogLevel : int;
|
||||
|
||||
enum GEBufferFormat : uint8_t;
|
||||
struct DebugScreenshotDesc {
|
||||
const uint8_t *data;
|
||||
u32 stride;
|
||||
u32 height;
|
||||
GEBufferFormat format;
|
||||
};
|
||||
void Core_SendDebugOutput(LogLevel level, std::string_view string);
|
||||
void Core_SendDebugScreenshot(const DebugScreenshotDesc &desc);
|
||||
void Core_RegisterDebugOutputListeners(std::function<void(std::string_view)> listener, std::function<void(const DebugScreenshotDesc &)> screenshotListener);
|
||||
|
||||
class MIPSState;
|
||||
// Shortcut, just calls Core_MemoryException with automatically determined parameters (function name, etc).
|
||||
void Core_MemoryExceptionHLE(MIPSState *mips, u32 address, u32 accessSize, MemoryExceptionType type);
|
||||
|
||||
@@ -569,7 +569,7 @@ void NotifyMemInfoCopy(uint32_t destPtr, uint32_t srcPtr, uint32_t size, const c
|
||||
info.pc = currentMIPS->pc;
|
||||
|
||||
// Store the prefix for now. The correct tag will be calculated on flush.
|
||||
info.tagLen = std::min(sizeof(info.tag), prefixLen);
|
||||
info.tagLen = (uint8_t)std::min(sizeof(info.tag), prefixLen);
|
||||
memcpy(info.tag, prefix, info.tagLen);
|
||||
|
||||
std::lock_guard<std::mutex> guard(pendingWriteMutex);
|
||||
|
||||
@@ -940,7 +940,7 @@ int sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync)
|
||||
}
|
||||
}
|
||||
|
||||
bool __DisplayGetFramebuf(PSPPointer<u8> *topaddr, u32 *linesize, u32 *pixelFormat, int latchedMode) {
|
||||
bool __DisplayGetFramebuf(PSPPointer<u8> *topaddr, u32 *linesize, GEBufferFormat *pixelFormat, int latchedMode) {
|
||||
const FrameBufferState &fbState = latchedMode == PSP_DISPLAY_SETBUF_NEXTFRAME ? latchedFramebuf : framebuf;
|
||||
if (topaddr != nullptr)
|
||||
(*topaddr).ptr = fbState.topaddr;
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
|
||||
enum GEBufferFormat : uint8_t;
|
||||
|
||||
void __DisplayInit();
|
||||
void __DisplayDoState(PointerWrap &p);
|
||||
void __DisplayShutdown();
|
||||
@@ -26,7 +28,7 @@ void __DisplayShutdown();
|
||||
void Register_sceDisplay();
|
||||
|
||||
// Get information about the current framebuffer.
|
||||
bool __DisplayGetFramebuf(PSPPointer<u8> *topaddr, u32 *linesize, u32 *pixelFormat, int mode);
|
||||
bool __DisplayGetFramebuf(PSPPointer<u8> *topaddr, u32 *linesize, GEBufferFormat *pixelFormat, int mode);
|
||||
void __DisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync);
|
||||
|
||||
// Call this when resuming to avoid a small speedup burst
|
||||
|
||||
+8
-7
@@ -2037,8 +2037,7 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o
|
||||
case EMULATOR_DEVCTL__SEND_OUTPUT:
|
||||
if (Memory::IsValidRange(argAddr, argLen)) {
|
||||
std::string data(Memory::GetCharPointerUnchecked(argAddr), argLen);
|
||||
if (!System_SendDebugOutput(data))
|
||||
DEBUG_LOG(Log::sceIo, "%s", data.c_str());
|
||||
Core_SendDebugOutput(LogLevel::LINFO, data);
|
||||
if (PSP_CoreParameter().collectDebugOutput)
|
||||
*PSP_CoreParameter().collectDebugOutput += data;
|
||||
}
|
||||
@@ -2055,12 +2054,14 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o
|
||||
|
||||
case EMULATOR_DEVCTL__EMIT_SCREENSHOT:
|
||||
{
|
||||
PSPPointer<u8> topaddr;
|
||||
u32 linesize;
|
||||
|
||||
__DisplayGetFramebuf(&topaddr, &linesize, nullptr, 0);
|
||||
// TODO: Add a high-res path for screenshots, and maybe a way to specify the filename.
|
||||
// TODO: Convert based on pixel format / mode / something?
|
||||
System_SendDebugScreenshot(&topaddr[0], linesize, 272);
|
||||
DebugScreenshotDesc desc;
|
||||
PSPPointer<u8> topaddr;
|
||||
__DisplayGetFramebuf(&topaddr, &desc.stride, &desc.format, 0);
|
||||
desc.data = &topaddr[0];
|
||||
desc.height = 272;
|
||||
Core_SendDebugScreenshot(desc);
|
||||
return hleLogDebug(Log::sceIo, 0);
|
||||
}
|
||||
case EMULATOR_DEVCTL__TOGGLE_FASTFORWARD:
|
||||
|
||||
@@ -1913,10 +1913,12 @@ int __KernelGPUReplay() {
|
||||
}
|
||||
|
||||
if (result == GPURecord::ReplayResult::Done && PSP_CoreParameter().headLess && !PSP_CoreParameter().startBreak) {
|
||||
DebugScreenshotDesc desc;
|
||||
PSPPointer<u8> topaddr;
|
||||
u32 linesize = 512;
|
||||
__DisplayGetFramebuf(&topaddr, &linesize, nullptr, 0);
|
||||
System_SendDebugScreenshot(&topaddr[0], linesize, 272);
|
||||
__DisplayGetFramebuf(&topaddr, &desc.stride, &desc.format, 0);
|
||||
desc.data = &topaddr[0];
|
||||
desc.height = 272;
|
||||
Core_SendDebugScreenshot(desc);
|
||||
Core_Stop();
|
||||
}
|
||||
|
||||
|
||||
+11
-67
@@ -32,6 +32,8 @@
|
||||
#include "Core/Util/DisArm64.h"
|
||||
#elif PPSSPP_ARCH(ARM)
|
||||
#include "ext/disarm.h"
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
#include "ext/riscv-disas.h"
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
#include "ext/loongarch-disasm.h"
|
||||
#endif
|
||||
@@ -184,67 +186,12 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
|
||||
ArmLSInstructionInfo info{};
|
||||
success = ArmAnalyzeLoadStore((uint32_t)codePtr, word, &info);
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
// TODO: Put in a disassembler.
|
||||
struct RiscVLSInstructionInfo {
|
||||
int instructionSize;
|
||||
bool isIntegerLoadStore;
|
||||
bool isFPLoadStore;
|
||||
int size;
|
||||
bool isMemoryWrite;
|
||||
};
|
||||
|
||||
uint32_t word;
|
||||
memcpy(&word, codePtr, 4);
|
||||
|
||||
// To ignore the access, we need to disassemble the instruction and modify context->CTX_PC
|
||||
RiscVLSInstructionInfo info{};
|
||||
// Compressed instructions have low bits 00, 01, or 10.
|
||||
info.instructionSize = (word & 3) == 3 ? 4 : 2;
|
||||
success = RiscVAnalyzeLoadStore((uint64_t)codePtr, word, &info);
|
||||
instructionSize = info.instructionSize;
|
||||
|
||||
success = true;
|
||||
switch (word & 0x7F) {
|
||||
case 3:
|
||||
info.isIntegerLoadStore = true;
|
||||
info.size = 1 << ((word >> 12) & 3);
|
||||
break;
|
||||
case 7:
|
||||
info.isFPLoadStore = true;
|
||||
info.size = 1 << ((word >> 12) & 3);
|
||||
break;
|
||||
case 35:
|
||||
info.isIntegerLoadStore = true;
|
||||
info.isMemoryWrite = true;
|
||||
info.size = 1 << ((word >> 12) & 3);
|
||||
break;
|
||||
case 39:
|
||||
info.isFPLoadStore = true;
|
||||
info.isMemoryWrite = true;
|
||||
info.size = 1 << ((word >> 12) & 3);
|
||||
break;
|
||||
default:
|
||||
// Compressed instruction.
|
||||
switch (word & 0x6003) {
|
||||
case 0x4000:
|
||||
case 0x4002:
|
||||
case 0x6000:
|
||||
case 0x6002:
|
||||
info.isIntegerLoadStore = true;
|
||||
info.size = (word & 0x2000) != 0 ? 8 : 4;
|
||||
info.isMemoryWrite = (word & 0x8000) != 0;
|
||||
break;
|
||||
case 0x2000:
|
||||
case 0x2002:
|
||||
info.isFPLoadStore = true;
|
||||
info.size = 8;
|
||||
info.isMemoryWrite = (word & 0x8000) != 0;
|
||||
break;
|
||||
default:
|
||||
// Not a read or a write.
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
uint32_t word;
|
||||
memcpy(&word, codePtr, 4);
|
||||
@@ -308,11 +255,11 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
|
||||
std::string infoString = "";
|
||||
std::string temp;
|
||||
if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(codePtr, temp)) {
|
||||
infoString += temp + "\n";
|
||||
infoString += temp + " ";
|
||||
}
|
||||
temp.clear();
|
||||
if (DisassembleNativeAt(codePtr, instructionSize, &temp)) {
|
||||
infoString += temp + "\n";
|
||||
infoString += "(" + temp + ") ";
|
||||
}
|
||||
|
||||
// Either bIgnoreBadMemAccess is off, or we failed recovery analysis.
|
||||
@@ -320,7 +267,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
|
||||
uint32_t approximatePC = currentMIPS->pc;
|
||||
// TODO: Determine access size from the disassembled native instruction. We have some partial info already,
|
||||
// just need to clean it up.
|
||||
Core_MemoryException(guestAddress, 0, approximatePC, type, infoString);
|
||||
Core_MemoryException(guestAddress, info.OperandSizeInBytes(), approximatePC, type, infoString);
|
||||
|
||||
// There's a small chance we can resume from this type of crash.
|
||||
g_lastCrashAddress = codePtr;
|
||||
@@ -333,7 +280,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
|
||||
context->CTX_PC = crashHandler;
|
||||
else
|
||||
handled = false;
|
||||
ERROR_LOG(Log::MemMap, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str());
|
||||
// ERROR_LOG(Log::MemMap, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str());
|
||||
}
|
||||
|
||||
inCrashHandler = false;
|
||||
@@ -373,16 +320,13 @@ std::vector<MIPSStackWalk::StackFrame> WalkCurrentStack(int threadID) {
|
||||
std::string FormatStackTrace(const std::vector<MIPSStackWalk::StackFrame> &frames) {
|
||||
std::stringstream str;
|
||||
for (const auto &frame : frames) {
|
||||
if (frame.pc == 0xFFFFFFFF) {
|
||||
// Bottom of stack, probably.
|
||||
continue;
|
||||
}
|
||||
const u32 frameEntry = frame.entry == 0xFFFFFFFF ? 0 : frame.entry;
|
||||
std::string desc = g_symbolMap->GetDescription(frame.entry);
|
||||
char moduleDesc[96];
|
||||
if (DescribeKernelModuleAddress(frame.entry, moduleDesc, sizeof(moduleDesc))) {
|
||||
str << StringFromFormat("%s [%s] (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), moduleDesc, frame.entry, frame.pc - frame.entry, frame.pc, frame.sp);
|
||||
str << StringFromFormat("%s [%s] (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), moduleDesc, frameEntry, frame.pc - frameEntry, frame.pc, frame.sp);
|
||||
} else {
|
||||
str << StringFromFormat("%s (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), frame.entry, frame.pc - frame.entry, frame.pc, frame.sp);
|
||||
str << StringFromFormat("%s (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), frameEntry, frame.pc - frameEntry, frame.pc, frame.sp);
|
||||
}
|
||||
}
|
||||
return str.str();
|
||||
|
||||
+3
-1
@@ -36,6 +36,7 @@
|
||||
#include "Common/System/OSD.h"
|
||||
#include "Common/Data/Text/I18n.h"
|
||||
#include "Common/File/Path.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/File/DirListing.h"
|
||||
#include "Common/File/AndroidContentURI.h"
|
||||
@@ -359,7 +360,8 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
|
||||
default:
|
||||
{
|
||||
// Trying to boot other things lands us here. We need to return a sensible error string.
|
||||
ERROR_LOG(Log::Loader, "CPU_Init didn't recognize file. %s", errorString->c_str());
|
||||
ERROR_LOG(Log::Loader, "CPU_Init didn't recognize file: %s. %s", fileLoader->GetPath().c_str(), errorString->c_str());
|
||||
Core_SendDebugOutput(LogLevel::LINFO, StringFromFormat("File not recognized: %s. %s", fileLoader->GetPath().c_str(), errorString->c_str()));
|
||||
auto sy = GetI18NCategory(I18NCat::SYSTEM);
|
||||
if (errorString->empty()) {
|
||||
*errorString = sy->T("Not a PSP game");
|
||||
|
||||
@@ -41,7 +41,7 @@ struct Arm64LSInstructionInfo {
|
||||
int Rn;
|
||||
int Rm;
|
||||
|
||||
// TODO: more.
|
||||
int OperandSizeInBytes() const { return 1 << size; }
|
||||
};
|
||||
|
||||
bool Arm64AnalyzeLoadStore(uint64_t addr, uint32_t op, Arm64LSInstructionInfo *info);
|
||||
@@ -1623,6 +1623,7 @@ VertexDecoderJitCache::VertexDecoderJitCache()
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Clear() {
|
||||
// TODO: These should check CoreParameter instead.
|
||||
if (g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR) {
|
||||
ClearCodeSpace(0);
|
||||
}
|
||||
|
||||
@@ -798,7 +798,8 @@ void Recorder::NotifyBeginFrame() {
|
||||
CheckEdramTrans();
|
||||
struct DisplayBufData {
|
||||
PSPPointer<u8> topaddr;
|
||||
u32 linesize, pixelFormat;
|
||||
u32 linesize;
|
||||
GEBufferFormat pixelFormat;
|
||||
};
|
||||
|
||||
DisplayBufData disp;
|
||||
|
||||
@@ -1240,9 +1240,6 @@ void System_Notify(SystemNotification notification) {
|
||||
}
|
||||
}
|
||||
|
||||
bool System_SendDebugOutput(std::string_view data) { return false; }
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {}
|
||||
|
||||
void UpdateWindowState(SDL_Window *window) {
|
||||
SDL_SetWindowTitle(window, g_windowState.title.c_str());
|
||||
if (g_windowState.applyFullScreenNextFrame) {
|
||||
|
||||
@@ -268,7 +268,7 @@ void DrawDisplayWindow(ImConfig &cfg, FramebufferManagerCommon *framebufferManag
|
||||
|
||||
PSPPointer<u8> topaddr;
|
||||
u32 linesize;
|
||||
u32 pixelFormat;
|
||||
GEBufferFormat pixelFormat;
|
||||
|
||||
__DisplayGetFramebuf(&topaddr, &linesize, &pixelFormat, cfg.displayLatched);
|
||||
|
||||
|
||||
@@ -402,9 +402,6 @@ std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
|
||||
}
|
||||
}
|
||||
|
||||
bool System_SendDebugOutput(std::string_view data) { return false; }
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {}
|
||||
|
||||
extern AudioBackend *g_audioBackend;
|
||||
|
||||
int64_t System_GetPropertyInt(SystemProperty prop) {
|
||||
|
||||
@@ -890,9 +890,6 @@ static std::string GetDefaultLangRegion() {
|
||||
}
|
||||
}
|
||||
|
||||
bool System_SendDebugOutput(std::string_view data) { return false; }
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {}
|
||||
|
||||
static const int EXIT_CODE_VULKAN_WORKS = 42;
|
||||
|
||||
#ifndef _DEBUG
|
||||
|
||||
@@ -291,9 +291,6 @@ void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {
|
||||
}
|
||||
}
|
||||
|
||||
bool System_SendDebugOutput(std::string_view data) { return false; }
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {}
|
||||
|
||||
std::string System_GetProperty(SystemProperty prop) {
|
||||
switch (prop) {
|
||||
case SYSPROP_NAME:
|
||||
|
||||
@@ -163,7 +163,7 @@ static void waves_synth(Atrac3pWaveSynthParams *synth_param,
|
||||
: 1.0f);
|
||||
|
||||
inc = wave_param->freq_index;
|
||||
pos = DEQUANT_PHASE(wave_param->phase_index) - (reg_offset ^ 128) * inc & 2047;
|
||||
pos = (DEQUANT_PHASE(wave_param->phase_index) - (reg_offset ^ 128) * inc) & 2047;
|
||||
|
||||
/* waveform generation */
|
||||
for (i = 0; i < 128; i++) {
|
||||
|
||||
@@ -45,6 +45,7 @@ struct ArmLSInstructionInfo {
|
||||
int Rm;
|
||||
|
||||
// TODO: more.
|
||||
int OperandSizeInBytes() const { return 1 << size; }
|
||||
};
|
||||
|
||||
bool ArmAnalyzeLoadStore(uint32_t addr, uint32_t op, ArmLSInstructionInfo *info);
|
||||
@@ -2761,6 +2761,8 @@ struct LoongArch64LSInstructionInfo {
|
||||
bool isFPLoadStore;
|
||||
int size; // 0 = 8-bit, 1 = 16-bit, 2 = 32-bit, 3 = 64-bit
|
||||
bool isMemoryWrite;
|
||||
|
||||
int OperandSizeInBytes() const { return size; }
|
||||
};
|
||||
|
||||
uint32_t la_assemble(Ins *ins);
|
||||
|
||||
@@ -2526,3 +2526,54 @@ void riscv_disasm_inst(char *buf, size_t buflen, rv_isa isa, uint64_t pc, rv_ins
|
||||
decode_inst_lift_pseudo(&dec);
|
||||
decode_inst_format(buf, buflen, 32, &dec);
|
||||
}
|
||||
|
||||
/* PPSSPP: analyze a load/store instruction, used by the JIT crash handler */
|
||||
|
||||
bool RiscVAnalyzeLoadStore(uint64_t addr, uint32_t word, RiscVLSInstructionInfo *info)
|
||||
{
|
||||
*info = {};
|
||||
// Compressed instructions have low bits 00, 01, or 10.
|
||||
info->instructionSize = (word & 3) == 3 ? 4 : 2;
|
||||
|
||||
switch (word & 0x7F) {
|
||||
case 3:
|
||||
info->isIntegerLoadStore = true;
|
||||
info->size = 1 << ((word >> 12) & 3);
|
||||
return true;
|
||||
case 7:
|
||||
info->isFPLoadStore = true;
|
||||
info->size = 1 << ((word >> 12) & 3);
|
||||
return true;
|
||||
case 35:
|
||||
info->isIntegerLoadStore = true;
|
||||
info->isMemoryWrite = true;
|
||||
info->size = 1 << ((word >> 12) & 3);
|
||||
return true;
|
||||
case 39:
|
||||
info->isFPLoadStore = true;
|
||||
info->isMemoryWrite = true;
|
||||
info->size = 1 << ((word >> 12) & 3);
|
||||
return true;
|
||||
default:
|
||||
// Compressed instruction.
|
||||
switch (word & 0x6003) {
|
||||
case 0x4000:
|
||||
case 0x4002:
|
||||
case 0x6000:
|
||||
case 0x6002:
|
||||
info->isIntegerLoadStore = true;
|
||||
info->size = (word & 0x2000) != 0 ? 8 : 4;
|
||||
info->isMemoryWrite = (word & 0x8000) != 0;
|
||||
return true;
|
||||
case 0x2000:
|
||||
case 0x2002:
|
||||
info->isFPLoadStore = true;
|
||||
info->size = 8;
|
||||
info->isMemoryWrite = (word & 0x8000) != 0;
|
||||
return true;
|
||||
default:
|
||||
// Not a read or a write.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,4 +594,16 @@ size_t riscv_inst_length(rv_inst inst);
|
||||
void riscv_inst_fetch(const uint8_t *data, rv_inst *instp, size_t *length);
|
||||
void riscv_disasm_inst(char *buf, size_t buflen, rv_isa isa, uint64_t pc, rv_inst inst);
|
||||
|
||||
// PPSSPP: information about a load/store instruction, used by the JIT crash handler.
|
||||
struct RiscVLSInstructionInfo {
|
||||
int instructionSize;
|
||||
bool isIntegerLoadStore;
|
||||
bool isFPLoadStore;
|
||||
int size; // Size of the access, in bytes (1, 2, 4, or 8).
|
||||
bool isMemoryWrite;
|
||||
int OperandSizeInBytes() const { return size; }
|
||||
};
|
||||
|
||||
bool RiscVAnalyzeLoadStore(uint64_t addr, uint32_t word, RiscVLSInstructionInfo *info);
|
||||
|
||||
#endif
|
||||
+32
-35
@@ -9,6 +9,8 @@
|
||||
// > -l --graphics=vulkan --screenshot-save=vt_ref.bmp "D:\PSP ISO\dump\Depth\11578 Virtua Tennis pause menu ULES00126_0002.zip" --resolution-scale=2
|
||||
// Example command line for messing with the vsh:
|
||||
// > -l --vsh --memread=break --memwrite=break --break=break
|
||||
// Example command line for looking at crash output:
|
||||
// > -l --graphics=software pspautotests/tests/cpu/crash/crash_read_f32.prx
|
||||
//
|
||||
// NOTE: In MSVC, don't forget to set the working directory to $ProjectDir\.. in debug settings.
|
||||
|
||||
@@ -144,10 +146,6 @@ void FlushDebugOutput() {
|
||||
}
|
||||
}
|
||||
|
||||
void SetWriteDebugOutput(bool flag) {
|
||||
g_writeDebugOutput = flag;
|
||||
}
|
||||
|
||||
void SetComparisonScreenshot(const Path &filename, double maxError) {
|
||||
g_comparisonScreenshot = filename;
|
||||
g_maxScreenshotError = maxError;
|
||||
@@ -174,31 +172,27 @@ void SendDebugOutput(std::string_view output) {
|
||||
}
|
||||
}
|
||||
|
||||
bool System_SendDebugOutput(std::string_view data) {
|
||||
SendDebugOutput(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendAndCollectOutput(const std::string &output) {
|
||||
void SendAndCollectOutput(std::string_view output) {
|
||||
SendDebugOutput(output);
|
||||
if (PSP_CoreParameter().collectDebugOutput) {
|
||||
*PSP_CoreParameter().collectDebugOutput += output;
|
||||
}
|
||||
}
|
||||
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {
|
||||
const u8 *pixbuf = (const u8 *)data;
|
||||
u32 w = width;
|
||||
u32 h = height;
|
||||
void SendDebugScreenshot(const DebugScreenshotDesc &desc) {
|
||||
const u8 *pixbuf = (const u8 *)desc.data;
|
||||
u32 w = desc.stride;
|
||||
u32 h = desc.height;
|
||||
|
||||
// We ignore the current framebuffer parameters and just grab the full screen.
|
||||
// TOOD: Uh, why not use them? They should be the same.
|
||||
const static u32 FRAME_STRIDE = 512;
|
||||
const static u32 FRAME_WIDTH = 480;
|
||||
const static u32 FRAME_HEIGHT = 272;
|
||||
|
||||
GPUDebugBuffer buffer;
|
||||
gpu->GetCurrentFramebuffer(buffer, GPU_DBG_FRAMEBUF_DISPLAY);
|
||||
const std::vector<u32> pixels = TranslateDebugBufferToCompare(&buffer, 512, 272);
|
||||
const std::vector<u32> pixels = TranslateDebugBufferToCompare(&buffer, FRAME_STRIDE, FRAME_HEIGHT);
|
||||
|
||||
// If a screenshot save path is set, save unconditionally.
|
||||
if (!g_screenshotSavePath.empty()) {
|
||||
@@ -301,8 +295,9 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar
|
||||
g_screenshotFailed = false;
|
||||
|
||||
std::string output;
|
||||
if (opt.compare || opt.bench)
|
||||
if (opt.compare || opt.bench) {
|
||||
coreParameter.collectDebugOutput = &output;
|
||||
}
|
||||
|
||||
if (!PSP_InitStart(coreParameter)) {
|
||||
// Shouldn't really happen anymore, the errors happen later in PSP_InitUpdate.
|
||||
@@ -397,7 +392,7 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar
|
||||
passed = CompareOutput(coreParameter.fileToStart, output, opt.verbose, opt.printEqualLines);
|
||||
}
|
||||
|
||||
// Screenshot comparison failures are recorded in System_SendDebugScreenshot.
|
||||
// Screenshot comparison failures are recorded in SendDebugScreenshot.
|
||||
if (!g_comparisonScreenshot.empty() && g_screenshotFailed) {
|
||||
passed = false;
|
||||
}
|
||||
@@ -571,8 +566,6 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
bool fullLog = cmdLineOptions.enableLogging.value_or(false);
|
||||
const char *stateToLoad = cmdLineOptions.stateToLoad.has_value() ? cmdLineOptions.stateToLoad.value().c_str() : nullptr;
|
||||
GPUCore gpuCore = GPUCORE_SOFTWARE;
|
||||
CPUCore cpuCore = CPUCore::JIT;
|
||||
bool oldAtrac = false;
|
||||
bool outputDebugStringLog = cmdLineOptions.odsLog.value_or(false);
|
||||
|
||||
@@ -581,10 +574,6 @@ int main(int argc, const char* argv[]) {
|
||||
std::string mountIso = cmdLineOptions.mountIso.value_or("");
|
||||
std::string mountRoot;
|
||||
|
||||
if (cmdLineOptions.cpuCore.has_value()) {
|
||||
cpuCore = cmdLineOptions.cpuCore.value();
|
||||
}
|
||||
|
||||
if (cmdLineOptions.root.has_value()) {
|
||||
mountRoot = cmdLineOptions.root.value().c_str();
|
||||
}
|
||||
@@ -621,6 +610,8 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS | RestoreSettingsBits::CONTROLS | RestoreSettingsBits::RECENT, false);
|
||||
|
||||
Core_RegisterDebugOutputListeners(&SendDebugOutput, &SendDebugScreenshot);
|
||||
|
||||
// Needs to be after log so we don't interfere with test output.
|
||||
g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count);
|
||||
|
||||
@@ -630,10 +621,10 @@ int main(int argc, const char* argv[]) {
|
||||
// ApplyToConfig() below, so a matching command line flag can still override any of it -
|
||||
// ApplyToConfig() always has the final say on the settings in g_Config.
|
||||
//
|
||||
// Somehow this affects the test execution of pspautotests/tests/gpu/vertices/morph.prx, even though
|
||||
// we actually set the cpu core in CoreParameter below. Probably because we end up using the JIT vs non-JIT
|
||||
// vertex decoder.
|
||||
g_Config.iCpuCore = 0;
|
||||
// This affects the test execution of pspautotests/tests/gpu/vertices/morph.prx, even though
|
||||
// we actually set the cpu core in CoreParameter below.
|
||||
// The check that decides that is in the DrawEngineCommon constructor.
|
||||
g_Config.iCpuCore = (int)CPUCore::INTERPRETER;
|
||||
|
||||
// NOTE: In headless mode, we never save the config. This is just for this run.
|
||||
g_Config.iDumpFileTypes = 0;
|
||||
@@ -674,18 +665,23 @@ int main(int argc, const char* argv[]) {
|
||||
g_Config.internalDataDirectory.clear();
|
||||
g_Config.bUseOldAtrac = oldAtrac;
|
||||
g_Config.iForceEnableHLE = 0xFFFFFFFF; // Run all modules as HLE. We don't have anything to load in this context.
|
||||
g_Config.bSkipDeadbeefFilling = false;
|
||||
|
||||
// ApplyToConfig() has the final say, applied after RestoreDefaults() and the headless
|
||||
// overrides above, so a matching command line flag always wins.
|
||||
cmdLineOptions.ApplyToConfig();
|
||||
|
||||
// This looks contradictory to the above. But, this preserves the old test behavior which apparently ran the JIT for the CPU
|
||||
// but ended up running software vertex decoding due to the setting in g_Config. Yeah, it's a mess.
|
||||
CPUCore cpuCore = CPUCore::JIT;
|
||||
if (cmdLineOptions.cpuCore.has_value()) {
|
||||
cpuCore = cmdLineOptions.cpuCore.value();
|
||||
}
|
||||
|
||||
GPUCore gpuCore = GPUCORE_SOFTWARE;
|
||||
// Translate backend to core. We probably should consider merging these enums.
|
||||
if (!g_Config.bSoftwareRendering) {
|
||||
if (!cmdLineOptions.gpuBackend.has_value()) {
|
||||
fprintf(stderr, "No graphics backend specified, but software rendering is disabled. Use --graphics=software, gles, directx11, or vulkan.\n");
|
||||
return 1;
|
||||
}
|
||||
switch (cmdLineOptions.gpuBackend.value()) {
|
||||
switch ((GPUBackend)g_Config.iGPUBackend) {
|
||||
case GPUBackend::OPENGL:
|
||||
gpuCore = GPUCORE_GLES;
|
||||
break;
|
||||
@@ -730,8 +726,8 @@ int main(int argc, const char* argv[]) {
|
||||
// TODO: This whole function should be refactored to set up CoreParameter in one place,
|
||||
// but not now.
|
||||
CoreParameter coreParameter;
|
||||
coreParameter.cpuCore = cpuCore; // apprently this gets overwritten somehow by g_Config above.
|
||||
coreParameter.gpuCore = gpuCore;
|
||||
coreParameter.cpuCore = (CPUCore)cpuCore;
|
||||
coreParameter.gpuCore = (GPUCore)gpuCore;
|
||||
coreParameter.graphicsContext = graphicsContext;
|
||||
coreParameter.enableSound = false;
|
||||
coreParameter.mountIso = mountIso.empty() ? Path() : Path(mountIso);
|
||||
@@ -786,8 +782,9 @@ int main(int argc, const char* argv[]) {
|
||||
if (cmdLineOptions.screenshotSaveKeepAlpha.has_value()) {
|
||||
g_screenshotSaveKeepAlpha = cmdLineOptions.screenshotSaveKeepAlpha.value();
|
||||
}
|
||||
|
||||
SetWriteFailureScreenshot(!getenv("GITHUB_ACTIONS") && !testOptions.bench);
|
||||
SetWriteDebugOutput(!testOptions.compare && !testOptions.bench);
|
||||
g_writeDebugOutput = !testOptions.compare && !testOptions.bench;
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
// For some reason the debugger installs it with this name?
|
||||
|
||||
@@ -664,9 +664,6 @@ AudioBackend *System_CreateAudioBackend() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool System_SendDebugOutput(std::string_view data) { return false; }
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
version = [[[UIDevice currentDevice] systemVersion] UTF8String];
|
||||
if (1 != sscanf(version.c_str(), "%d", &g_iosVersionMajor)) {
|
||||
|
||||
@@ -1939,9 +1939,6 @@ int64_t System_GetPropertyInt(SystemProperty prop) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool System_SendDebugOutput(std::string_view data) { return false; }
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {}
|
||||
|
||||
float System_GetPropertyFloat(SystemProperty prop) {
|
||||
switch (prop) {
|
||||
case SYSPROP_DISPLAY_REFRESH_RATE:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
|
||||
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Common/x64Analyzer.h"
|
||||
#include "Common/x64Emitter.h"
|
||||
#include "Core/MIPS/x86/RegCacheFPU.h"
|
||||
#include "Core/MIPS/x86/Jit.h"
|
||||
@@ -21,6 +22,22 @@ static bool CheckLast(const Gen::XEmitter &emit, const char *comp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Emits nothing itself - runs the x64 crash-handler instruction analyzer (Common/x64Analyzer.cpp)
|
||||
// on the instruction most recently emitted (from prevStart to the emitter's current position),
|
||||
// and checks that it decoded it the way we expect.
|
||||
static bool CheckAnalyze(const Gen::XEmitter &emit, bool expectWrite, InstructionClass expectClass, int expectOperandSize, bool expectZeroExtend = false, bool expectSignExtend = false) {
|
||||
LSInstructionInfo info{};
|
||||
bool success = X86AnalyzeMOV(prevStart, info);
|
||||
EXPECT_TRUE(success);
|
||||
EXPECT_EQ_INT(info.instructionSize, (int)(emit.GetCodePointer() - prevStart));
|
||||
EXPECT_EQ_INT(info.isMemoryWrite, expectWrite);
|
||||
EXPECT_EQ_INT((int)info.instructionClass, (int)expectClass);
|
||||
EXPECT_EQ_INT(info.operandSizeInBytes, expectOperandSize);
|
||||
EXPECT_EQ_INT(info.zeroExtend, expectZeroExtend);
|
||||
EXPECT_EQ_INT(info.signExtend, expectSignExtend);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PrintLast(const Gen::XEmitter &emit) {
|
||||
for (const u8 *p = prevStart; p < emit.GetCodePointer(); p++) {
|
||||
printf("%02x ", *p);
|
||||
@@ -47,6 +64,57 @@ bool TestX64Emitter() {
|
||||
|
||||
cpu_info.bAVX = prevAVX;
|
||||
|
||||
// Exercise Common/x64Analyzer.cpp (used by the JIT crash handler to figure out what a faulting
|
||||
// load/store instruction was doing) against instructions written by the emitter, for the most
|
||||
// common memory access instructions.
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOV(32, R(EAX), MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 4));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOV(32, MDisp(RCX, 4), R(EAX));
|
||||
RET(CheckAnalyze(emitter, true, InstructionClass::GPR, 4));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVZX(32, 8, EAX, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 1, true, false));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVZX(32, 16, EAX, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 2, true, false));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVSX(32, 8, EAX, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 1, false, true));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVSX(32, 16, EAX, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 2, false, true));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVSS(XMM0, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::FP, 4));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVSS(MDisp(RCX, 4), XMM0);
|
||||
RET(CheckAnalyze(emitter, true, InstructionClass::FP, 4));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVUPS(XMM0, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::FP_SIMD, 16));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVUPS(MDisp(RCX, 4), XMM0);
|
||||
RET(CheckAnalyze(emitter, true, InstructionClass::FP_SIMD, 16));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVAPS(XMM0, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::FP_SIMD, 16));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVAPS(MDisp(RCX, 4), XMM0);
|
||||
RET(CheckAnalyze(emitter, true, InstructionClass::FP_SIMD, 16));
|
||||
|
||||
// Just for checking.
|
||||
PrintLast(emitter);
|
||||
return true;
|
||||
|
||||
@@ -127,8 +127,6 @@ void System_RunOnMainThread(std::function<void()>) {}
|
||||
void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\0'; }
|
||||
void System_AudioClear() {}
|
||||
void System_AudioPushSamples(const s32 *audio, int numSamples, float volume) {}
|
||||
bool System_SendDebugOutput(std::string_view data) { return false; }
|
||||
void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {}
|
||||
std::vector<std::string> System_GetCameraDeviceList() { return std::vector<std::string>(); }
|
||||
|
||||
// Temporary hacks around annoying linking errors. Copied from Headless.
|
||||
|
||||
Reference in new issue
Block a user