Merge pull request #22072 from hrydgard/improve-headless-output

Improve crash message formatting, headless output
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-08-11 23:51:46 +02:00
commit 9a84bca2fc
33 files changed
+370 -184

No files matched your search

+1
View File
@@ -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);
+1 -1
View File
@@ -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));
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
+3 -1
View File
@@ -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
View File
@@ -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:
+5 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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");
+1 -1
View File
@@ -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);
+1
View File
@@ -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);
}
+2 -1
View File
@@ -798,7 +798,8 @@ void Recorder::NotifyBeginFrame() {
CheckEdramTrans();
struct DisplayBufData {
PSPPointer<u8> topaddr;
u32 linesize, pixelFormat;
u32 linesize;
GEBufferFormat pixelFormat;
};
DisplayBufData disp;
-3
View File
@@ -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) {
+1 -1
View File
@@ -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);
-3
View File
@@ -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) {
-3
View File
@@ -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
-3
View File
@@ -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:
+1 -1
View File
@@ -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++) {
+1
View File
@@ -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);
+2
View File
@@ -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);
+51
View File
@@ -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;
}
}
}
+12
View File
@@ -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
View File
@@ -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?
-3
View File
@@ -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)) {
-3
View File
@@ -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:
+68
View File
@@ -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;
-2
View File
@@ -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.