diff --git a/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h b/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h index 29e10ed..ab3c02b 100644 --- a/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h +++ b/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h @@ -1,15 +1,16 @@ #ifndef PS2RECOMP_ELF_ANALYZER_H #define PS2RECOMP_ELF_ANALYZER_H -#include "ps2recomp/types.h" #include "ps2recomp/elf_parser.h" #include "ps2recomp/r5900_decoder.h" +#include "ps2recomp/types.h" #include #include -#include #include -#include -#include +#include +#include +#include +#include namespace ps2recomp { @@ -31,29 +32,51 @@ namespace ps2recomp std::vector m_symbols; std::vector
m_sections; std::vector m_relocations; - - std::unordered_set m_libFunctions; // Library functions to stub - std::unordered_set m_skipFunctions; // Functions to skip - std::unordered_map m_patches; // Address -> instruction patches - - // Common PS2 library function names + + std::unordered_set m_libFunctions; + std::unordered_set m_skipFunctions; + std::unordered_map> m_functionDataUsage; + std::unordered_map m_commonDataAccess; + + std::map m_patches; + std::map m_patchReasons; + + std::unordered_map m_functionCFGs; + std::vector m_jumpTables; + std::unordered_map> m_functionCalls; + void initializeLibraryFunctions(); - - // Analysis methods void analyzeEntryPoint(); void analyzeLibraryFunctions(); - void analyzeCallGraph(); void analyzeDataUsage(); void identifyPotentialPatches(); - std::string escapeBackslashes(const std::string &path); - - // Helpers + void analyzeControlFlow(); + void detectJumpTables(); + void analyzePerformanceCriticalPaths(); + void identifyRecursiveFunctions(); + void analyzeRegisterUsage(); + void analyzeFunctionSignatures(); + void optimizePatches(); + + bool identifyMemcpyPattern(const Function &func); + bool identifyMemsetPattern(const Function &func); + bool identifyStringOperationPattern(const Function &func); + bool identifyMathPattern(const Function &func); + bool isSystemFunction(const std::string &name) const; bool isLibraryFunction(const std::string &name) const; - void decodeFunction(const Function &function); + std::vector decodeFunction(const Function &function); + CFG buildCFG(const Function &function); std::string formatAddress(uint32_t address) const; + std::string escapeBackslashes(const std::string &path); + bool hasMMIInstructions(const Function &function); + bool hasVUInstructions(const Function &function); + bool identifyFunctionType(const Function &function); + void categorizeFunction(Function &function); + uint32_t getSuccessor(const Instruction &inst, uint32_t currentAddr); + bool isSelfModifyingCode(const Function &function); + bool isLoopHeavyFunction(const Function &function); }; - } #endif // PS2RECOMP_ELF_ANALYZER_H \ No newline at end of file diff --git a/ps2xAnalyzer/src/elf_analyzer.cpp b/ps2xAnalyzer/src/elf_analyzer.cpp index abeb803..71e4886 100644 --- a/ps2xAnalyzer/src/elf_analyzer.cpp +++ b/ps2xAnalyzer/src/elf_analyzer.cpp @@ -3,6 +3,11 @@ #include #include #include +#include +#include +#include +#include +#include namespace fs = std::filesystem; @@ -41,14 +46,31 @@ namespace ps2recomp analyzeEntryPoint(); analyzeLibraryFunctions(); - analyzeCallGraph(); analyzeDataUsage(); identifyPotentialPatches(); + analyzeControlFlow(); + detectJumpTables(); + analyzePerformanceCriticalPaths(); + identifyRecursiveFunctions(); + analyzeRegisterUsage(); + analyzeFunctionSignatures(); + optimizePatches(); + + for (auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) == m_skipFunctions.end() && + m_libFunctions.find(func.name) == m_libFunctions.end()) + { + categorizeFunction(func); + func.instructions = decodeFunction(func); + } + } std::cout << "Analysis completed" << std::endl; std::cout << "- " << m_libFunctions.size() << " library functions to stub" << std::endl; std::cout << "- " << m_skipFunctions.size() << " functions to skip" << std::endl; std::cout << "- " << m_patches.size() << " potential patches identified" << std::endl; + std::cout << "- " << m_jumpTables.size() << " jump tables detected" << std::endl; return true; } @@ -67,7 +89,12 @@ namespace ps2recomp fs::path outputPathObj(outputPath); fs::path outputDir = outputPathObj.parent_path(); - std::string outputDirStr = outputDir.string() + "\\output\\"; + std::string outputDirStr = outputDir.string() + "/output/"; + + if (!fs::exists(outputDir / "output")) + { + fs::create_directory(outputDir / "output"); + } file << "# PS2Recomp configuration for: " << elfFileName << "\n"; file << "# Generated by ElfAnalyzer\n\n"; @@ -98,6 +125,30 @@ namespace ps2recomp } file << "]\n\n"; + if (!m_jumpTables.empty()) + { + file << "# Jump tables detected in the program\n"; + file << "[jump_tables]\n"; + + for (size_t i = 0; i < m_jumpTables.size(); ++i) + { + const auto &jt = m_jumpTables[i]; + file << "[[jump_tables.table]]\n"; + file << "address = \"0x" << std::hex << jt.address << "\"\n" + << std::dec; + file << "entries = [\n"; + + for (const auto &entry : jt.entries) + { + file << " { index = " << entry.index << ", target = \"0x" + << std::hex << entry.target << "\" },\n" + << std::dec; + } + + file << "]\n\n"; + } + } + if (!m_patches.empty()) { file << "# Patches to apply during recompilation\n"; @@ -107,20 +158,26 @@ namespace ps2recomp for (const auto &[address, value] : m_patches) { file << " { address = \"0x" << std::hex << address << "\", value = \"0x" - << std::hex << value << "\" }, # Identified potential patch\n"; + << std::hex << value << "\" }, # " << m_patchReasons[address] << "\n"; } file << "]\n\n"; } - // file << "# Function hook patches\n"; - // file << "#[[patches.hook]]\n"; - // file << "#function = \"printf\"\n"; - // file << "#code = '''\n"; - // file << "#// Custom printf implementation\n"; - // file << "#void printf(uint8_t* rdram, R5900Context* ctx) {\n"; - // file << "# // Implementation here\n"; - // file << "#}\n"; - // file << "#'''\n\n"; + file << "# Performance critical functions (may need manual optimization)\n"; + file << "[performance]\n"; + file << "critical = [\n"; + for (const auto &func : m_functions) + { + if (hasMMIInstructions(func) || hasVUInstructions(func)) + { + file << " \"" << func.name << "\", # Uses SIMD instructions\n"; + } + else if (isLoopHeavyFunction(func)) + { + file << " \"" << func.name << "\", # Contains heavy loops\n"; + } + } + file << "]\n\n"; std::cout << "Generated TOML configuration: " << outputPath << std::endl; return true; @@ -130,45 +187,153 @@ namespace ps2recomp { // Standard C library functions const std::vector stdLibFuncs = { - "printf", "sprintf", "snprintf", "fprintf", "vprintf", "vfprintf", - "malloc", "free", "calloc", "realloc", - "memcpy", "memset", "memmove", "memcmp", - "strcpy", "strncpy", "strcat", "strncat", - "strcmp", "strncmp", "strlen", "strstr", - "fopen", "fclose", "fread", "fwrite", "fseek", - "atoi", "atof", "rand", "srand"}; + // I/O functions + "printf", "sprintf", "snprintf", "fprintf", "vprintf", "vfprintf", "vsprintf", "vsnprintf", + "puts", "putchar", "getchar", "gets", "fgets", "fputs", "scanf", "fscanf", "sscanf", + + // Memory management + "malloc", "free", "calloc", "realloc", "aligned_alloc", "posix_memalign", + + // Memory manipulation + "memcpy", "memset", "memmove", "memcmp", "memchr", "bcopy", "bzero", + + // String manipulation + "strcpy", "strncpy", "strcat", "strncat", "strcmp", "strncmp", "strlen", "strstr", + "strchr", "strrchr", "strdup", "strtok", "strtok_r", "strerror", + + // File operations + "fopen", "fclose", "fread", "fwrite", "fseek", "ftell", "rewind", "fflush", + "fgetc", "fgets", "feof", "ferror", "clearerr", "fileno", "tmpfile", "remove", "rename", + "open", "close", "read", "write", "lseek", "stat", "fstat", + + // Type conversion + "atoi", "atol", "atoll", "atof", "strtol", "strtoul", "strtoll", "strtoull", "strtod", "strtof", + + // Math functions + "rand", "srand", "random", "srandom", "drand48", "sqrt", "pow", "exp", "log", "log10", + "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "sinh", "cosh", "tanh", + "floor", "ceil", "fabs", "fmod", "frexp", "ldexp", "modf", + + // Time functions + "time", "ctime", "clock", "difftime", "mktime", "localtime", "gmtime", "asctime", "strftime", + "gettimeofday", "nanosleep", "usleep", + + // Process control + "abort", "exit", "_exit", "atexit", "system", "getpid", "fork", "waitpid", + + // Misc + "qsort", "bsearch", "abs", "div", "labs", "ldiv", "llabs", "lldiv", + "isalnum", "isalpha", "isdigit", "islower", "isupper", "isspace", "tolower", "toupper", + "setjmp", "longjmp", "getenv", "setenv", "unsetenv"}; // PS2-specific system functions const std::vector ps2SysFuncs = { - "FlushCache", "EI", "DI", "SYNC", - "syscall", "ResetEE", "SetGsCrt", "Exit", - "LoadExecPS2", "ExecPS2", "GetThreadId", - "RFU009", "InitRCnt", "GetOsTick", "ResetRCnt", - "DisableFPUExceptions", "EnableFPUExceptions"}; + // EE Kernel + "FlushCache", "EI", "DI", "SYNC", "ExitThread", "SleepThread", "WakeupThread", + "syscall", "ResetEE", "SetGsCrt", "Exit", "LoadExecPS2", "ExecPS2", "GetThreadId", + "RFU009", "InitRCnt", "GetOsTick", "ResetRCnt", "ChangeThreadPriority", + "DisableFPUExceptions", "EnableFPUExceptions", "GetEEStatus", "SetEEStatus", + "GetCop0", "SetCop0", "GetCop1", "SetCop1", "Exception", + "CreateThread", "DeleteThread", "StartThread", "SuspendThread", "ResumeThread", + "GetThreadStatus", "ReferThreadStatus", "iWakeupThread", "iResumeThread", + "TerminateThread", "EnableIntc", "DisableIntc", "EnableDmac", "DisableDmac", + + // SIF + "SifInitRpc", "SifExitRpc", "SifBindRpc", "SifCallRpc", "SifRegisterRpc", + "SifCheckStatRpc", "SifSetRpcQueue", "SifRpcLoop", "SifGetOtherData", + "sceSifAddCmdHandler", "sceSifRemoveCmdHandler", "sceSifSendCmd", + "sceSifInitCmd", "sceSifExitCmd", "sceSifSetCmdBuffer", "SifDmaInit", + "SifSetDma", "SifSetDChain", "iSifSetDChain", "SifSetOneDma", + "sceSifDmaStat", "sceSifSetDmaIntr", "sceSifResetDmaIntr", + + // IOP + "PollSema", "WaitSema", "SignalSema", "iSignalSema", "CreateSema", + "DeleteSema", "iWaitSema", "PollEventFlag", "WaitEventFlag", "SignalEventFlag", + "iSignalEventFlag", "CreateEventFlag", "DeleteEventFlag", + + // Timer + "CreateAlarm", "iSetAlarm", "SetAlarm", "iReleaseAlarm", "ReleaseAlarm", + "USec2SysClock", "GetSystemTime", "SetSystemTime", "SysClock2USec"}; // PS2-specific library functions const std::vector ps2LibFuncs = { // GS - "GsSetCrt", "GsGetIMR", "GsPutIMR", "GsSetIMR", - "GsInit", "GsSyncV", "GsGetVideoMode", "GsSetVideoMode", + "GsSetCrt", "GsGetIMR", "GsPutIMR", "GsSetIMR", "GsInit", "GsSyncV", + "GsGetVideoMode", "GsSetVideoMode", "GsDefDispBuffer", "GsResetGraph", + "GsPutDrawEnv", "GsSetClip", "GsSetScissor", "GsInitialCursor", "GsDrawCursor", + "GsSetVmode", "GsSetXYOffset", "GsSetClear", "GsTest", "GsTexure", "GsDisplay", + "GsDrawPixel", "GsDrawLine", "GsDrawBox", "GsDrawTriangle", "GsDrawRect", + "GsDrawSprite", "GsPrimTriangle", "GsSwapFrame", "GsLoadImage", "GsPutImage", + "GsMakeIndex", "GsSetCombineMode", "GsSetVertexColor", "GsSetOrigin", // Pad - "PadInit", "PadPortOpen", "PadGetState", "PadRead", - "PadPortClose", "PadSetActAlign", "PadSetActDirect", - - // SIF - "SifInitRpc", "SifExitRpc", "SifBindRpc", "SifCallRpc", - "SifRegisterRpc", "SifCheckStatRpc", "SifSetRpcQueue", - "SifRpcLoop", "SifGetOtherData", + "PadInit", "PadPortOpen", "PadGetState", "PadRead", "PadSetMainMode", + "PadSetActDirect", "PadSetActAlign", "PadGetReqState", "PadInfoMode", + "PadInfoAct", "PadInfoComb", "PadSetActLED", "PadPortClose", "PadStateIntToStr", + "PadGetReqState", "PadInfoPressMode", "PadEnterPressMode", "PadExitPressMode", // IPU - "sceSifAddCmdHandler", "sceSifRemoveCmdHandler", "sceSifSendCmd", - "sceSifInitCmd", "sceSifExitCmd", "sceSifSetCmdBuffer"}; + "IPU_FDEC", "IPU_FRST", "IPU_SETIQ", "IPU_IDEC", "IPU_CSC", "IPU_PACK", + "IPU_VDEC", "IPU_FDTV", "IPU_SETTH", "IPU_Disable", "IPU_Reset", - // Add all to our library functions set + // DMA + "DmaGetChcr", "DmaGetMadr", "DmaGetTadr", "DmaGetQwc", "DmaGetRemaining", + "DmaSetChcr", "DmaSetMadr", "DmaSetTadr", "DmaSetQwc", "DmaStartTransfer", + "DmaEnableDma", "DmaDisableDma", "DmaTransferMem", "DmaWaitForTransfer", + + // CDVD + "CdInit", "CdDiskReady", "CdGetError", "CdGetToc", "CdReadSector", + "CdGetDiscType", "CdDiskReady", "CdTrayReq", "CdSync", "CdRead", + "CdStop", "CdSetmode", "CdSearchFile", "CdReadChain", "CdReadILINK", + + // Other libraries + "audsrv_init", "audsrv_adpcm_init", "audsrv_set_volume", "audsrv_play_adpcm", + "loadModules", "fioInit", "mcInit", "mtapInit", "padInit", "sioInit", + "ethPutIFAddr", "ethGetNetEther", "ethPutNetIFaddr", "ethGetHWaddr", + "ethUsrPkt_input", "ethIntrEnable", "ethSetupIF", "ethPutArpReq", + "ethPktToIF", "ethGetArpEntry", "ethAllocTxPacket", "ethFreeTxPacket"}; + + // Add new PS2-specific functions for more complete coverage + const std::vector additionalPs2Funcs = { + // VIF and FIFO functions + "VIF0_STAT", "VIF0_FBRST", "VIF0_ERR", "VIF0_MARK", "VIF0_CYCLE", "VIF0_MODE", + "VIF0_NUM", "VIF0_MASK", "VIF0_CODE", "VIF0_ITOPS", "VIF0_ITOP", "VIF0_R0", + "VIF0_R1", "VIF0_R2", "VIF0_R3", "VIF0_C0", "VIF0_C1", "VIF0_C2", "VIF0_C3", + "VIF1_STAT", "VIF1_FBRST", "VIF1_ERR", "VIF1_MARK", "VIF1_CYCLE", "VIF1_MODE", + "VIF1_NUM", "VIF1_MASK", "VIF1_CODE", "VIF1_ITOPS", "VIF1_BASE", "VIF1_OFST", + "VIF1_TOPS", "VIF1_ITOP", "VIF1_TOP", "VIF1_R0", "VIF1_R1", "VIF1_R2", "VIF1_R3", + "VIF1_C0", "VIF1_C1", "VIF1_C2", "VIF1_C3", + + // Graphics Synthesis functions + "GsGetGParam", "GsSetGParam", "GsGParam", "GsSetCBM", "GsCBM", "GsAddFB", + "GsAddFT", "GsFreeMem", "GsGetFBMem", "GsGetFTMem", "GsGetFT", "GsGetFB", + "GsSetRefView", "GsSetView", "GsGetActiveFrame", "GsSetDrawFrameBuffer", + "GsSetDisplayFrameBuffer", "GsSetZBufferAddress", "GsSetCLUT", "GsSetPaintMethod", + "GsCleanZBuffer", "GsSwapDispBuffer", "GsDrawSync", "GsVSync", + + // Audio functions + "SdInit", "SdSetParam", "SdGetParam", "SdSetSwitch", "SdGetSwitch", "SdSetAddr", + "SdGetAddr", "SdSetCoreAttr", "SdGetCoreAttr", "SdNote2Pitch", "SdPitch2Note", + "SdProcBatch", "SdProcBatchEx", "SdVoiceTrans", "SdBlockTrans", "SdVoiceTransStatus", + "SdBlockTransStatus", "iSdVoiceTrans", "iSdBlockTrans", "SdSetTransCallback", + "SdSetIRQCallback", "SdSetEffectAttr", "SdGetEffectAttr", "SdClearEffectWorkArea", + + // SPU2 functions + "sceSPU2Init", "sceSPU2Reset", "sceSPU2SetVolume", "sceSPU2GetVolume", + "sceSPU2SetReverb", "sceSPU2GetReverb", "sceSPU2SetTransferMode", + "sceSPU2GetTransferMode", "sceSPU2Write", "sceSPU2Read", "sceSPU2ReadDMA", + "sceSPU2WriteDMA", "sceSPU2SetVoiceAttributes", "sceSPU2GetVoiceAttributes", + + // Libmath + "sinf", "cosf", "tanf", "asinf", "acosf", "atanf", "atan2f", "sinhf", "coshf", "tanhf", + "sinl", "cosl", "tanl", "asinl", "acosl", "atanl", "atan2l", "sinhl", "coshl", "tanhl", + "sqrtf", "powf", "expf", "logf", "log10f"}; + + // Combine all library functions m_libFunctions.insert(stdLibFuncs.begin(), stdLibFuncs.end()); m_libFunctions.insert(ps2SysFuncs.begin(), ps2SysFuncs.end()); m_libFunctions.insert(ps2LibFuncs.begin(), ps2LibFuncs.end()); + m_libFunctions.insert(additionalPs2Funcs.begin(), additionalPs2Funcs.end()); } void ElfAnalyzer::analyzeEntryPoint() @@ -182,11 +347,47 @@ namespace ps2recomp std::cout << "Found entry point: " << it->name << " at 0x" << std::hex << it->start << std::dec << std::endl; m_skipFunctions.insert(it->name); - decodeFunction(*it); + + std::vector instructions = decodeFunction(*it); + + for (const auto &inst : instructions) + { + if (inst.opcode == OPCODE_JAL) + { + uint32_t target = (inst.address & 0xF0000000) | (inst.target << 2); + + for (const auto &func : m_functions) + { + if (func.start == target) + { + std::cout << "Found initialization call to: " << func.name << " at 0x" + << std::hex << inst.address << std::dec << std::endl; + + if (func.name.find("init") != std::string::npos || + func.name.find("Init") != std::string::npos) + { + m_skipFunctions.insert(func.name); + } + break; + } + } + } + } } else { std::cout << "Entry point not found" << std::endl; + + for (const auto &func : m_functions) + { + if (func.start == 0x100000 || func.start == 0x80100000) + { + std::cout << "Found potential entry point by address: " << func.name + << " at 0x" << std::hex << func.start << std::dec << std::endl; + m_skipFunctions.insert(func.name); + break; + } + } } } @@ -207,56 +408,1198 @@ namespace ps2recomp } } } - } - void ElfAnalyzer::analyzeCallGraph() - { - // functions called by the entry point are likely initialization and should be skipped for (const auto &func : m_functions) { - if (func.name.find("init") != std::string::npos || - func.name.find("Init") != std::string::npos || - func.name.find("start") != std::string::npos || - func.name.find("Start") != std::string::npos) + if (m_libFunctions.find(func.name) != m_libFunctions.end() || + m_skipFunctions.find(func.name) != m_skipFunctions.end()) { + continue; + } - m_skipFunctions.insert(func.name); + if (identifyMemcpyPattern(func)) + { + std::cout << "Identified function " << func.name << " as memcpy-like implementation" << std::endl; + m_libFunctions.insert(func.name); + } + else if (identifyMemsetPattern(func)) + { + std::cout << "Identified function " << func.name << " as memset-like implementation" << std::endl; + m_libFunctions.insert(func.name); + } + else if (identifyStringOperationPattern(func)) + { + std::cout << "Identified function " << func.name << " as string operation implementation" << std::endl; + m_libFunctions.insert(func.name); + } + else if (identifyMathPattern(func)) + { + std::cout << "Identified function " << func.name << " as math implementation" << std::endl; + m_libFunctions.insert(func.name); } } } void ElfAnalyzer::analyzeDataUsage() { - // TODO + std::cout << "Analyzing data usage patterns..." << std::endl; + + std::map> memoryAccessMap; + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end() || + m_libFunctions.find(func.name) != m_libFunctions.end()) + { + continue; + } + + std::vector instructions = decodeFunction(func); + + for (const auto &inst : instructions) + { + if (inst.opcode == OPCODE_LW || inst.opcode == OPCODE_SW || + inst.opcode == OPCODE_LB || inst.opcode == OPCODE_SB || + inst.opcode == OPCODE_LH || inst.opcode == OPCODE_SH || + inst.opcode == OPCODE_LBU || inst.opcode == OPCODE_LHU || + inst.opcode == OPCODE_LQ || inst.opcode == OPCODE_SQ) + { + // Check if the memory address involves $gp (global pointer) + if (inst.rs == 28) // $gp is typically register 28 + { + int16_t offset = static_cast(inst.immediate); + uint32_t gpValue = 0; + + // Try to find GP value in ELF sections + for (const auto §ion : m_sections) + { + if (section.name == ".got" || section.name == ".data" || + section.name == ".sdata" || section.name == ".sbss") + { + gpValue = section.address; + break; + } + } + + if (gpValue != 0) + { + uint32_t targetAddr = gpValue + offset; + memoryAccessMap[targetAddr].insert(func.name); + + auto symIt = std::find_if(m_symbols.begin(), m_symbols.end(), + [targetAddr](const Symbol &s) + { return !s.isFunction && s.address == targetAddr; }); + + if (symIt != m_symbols.end()) + { + std::cout << "Function " << func.name << " accesses data symbol " + << symIt->name << " at 0x" << std::hex << targetAddr + << std::dec << std::endl; + + m_functionDataUsage[func.name].insert(symIt->name); + } + else + { + // Try to find the symbol it belongs to, even if not exact match + for (const auto &sym : m_symbols) + { + if (!sym.isFunction && targetAddr >= sym.address && + targetAddr < sym.address + sym.size) + { + std::cout << "Function " << func.name << " accesses data within symbol " + << sym.name << " at offset 0x" << std::hex << (targetAddr - sym.address) + << std::dec << std::endl; + + m_functionDataUsage[func.name].insert(sym.name); + break; + } + } + } + } + } + // Also check for direct addressing with LUI+ADDIU combinations + else if (inst.opcode == OPCODE_LW || inst.opcode == OPCODE_SW) + { + // Look for the LUI instruction that sets up the high bits + uint32_t baseAddr = 0; + for (int i = 1; i <= 5 && static_cast(inst.address) - i * 4 >= static_cast(func.start); i++) + { + uint32_t prevAddr = inst.address - i * 4; + uint32_t prevInst = m_elfParser->readWord(prevAddr); + + // Check if it's a LUI instruction for the same register + if (OPCODE(prevInst) == OPCODE_LUI && RT(prevInst) == inst.rs) + { + baseAddr = IMMEDIATE(prevInst) << 16; + break; + } + } + + if (baseAddr != 0) + { + uint32_t targetAddr = baseAddr + static_cast(inst.immediate); + + for (const auto §ion : m_sections) + { + if (targetAddr >= section.address && targetAddr < section.address + section.size) + { + auto symIt = std::find_if(m_symbols.begin(), m_symbols.end(), + [targetAddr](const Symbol &s) + { return !s.isFunction && s.address <= targetAddr && + s.address + s.size > targetAddr; }); + + if (symIt != m_symbols.end()) + { + std::cout << "Function " << func.name << " directly accesses " + << (inst.opcode == OPCODE_LW ? "reads from" : "writes to") + << " data symbol " << symIt->name + << " at 0x" << std::hex << targetAddr << std::dec << std::endl; + + m_functionDataUsage[func.name].insert(symIt->name); + } + break; + } + } + } + } + } + } + } + + // Identify commonly accessed data (potential global structures) + for (const auto &[addr, funcs] : memoryAccessMap) + { + if (funcs.size() > 3) // If multiple functions access this data + { + std::string dataName = formatAddress(addr); + + auto symIt = std::find_if(m_symbols.begin(), m_symbols.end(), + [addr](const Symbol &s) + { return !s.isFunction && s.address == addr; }); + + if (symIt != m_symbols.end()) + { + dataName = symIt->name; + } + + std::cout << "Common data: " << dataName << " at 0x" << std::hex << addr + << std::dec << " accessed by " << funcs.size() << " functions" << std::endl; + + m_commonDataAccess[addr] = dataName; + } + } + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end() || + m_libFunctions.find(func.name) != m_libFunctions.end()) + { + continue; + } + + std::vector instructions = decodeFunction(func); + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if ((inst.opcode == OPCODE_SPECIAL && (inst.function == SPECIAL_SLL || inst.function == SPECIAL_SLLV)) || + (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_MULT)) + { + for (size_t j = i + 1; j < std::min(i + 5, instructions.size()); j++) + { + const auto &nextInst = instructions[j]; + + if ((nextInst.opcode == OPCODE_LW || nextInst.opcode == OPCODE_SW) && + nextInst.rs == inst.rd) + { + std::cout << "Found possible array access in function " << func.name + << " at 0x" << std::hex << nextInst.address << std::dec << std::endl; + break; + } + } + } + } + } } void ElfAnalyzer::identifyPotentialPatches() { - // This is a very basic implementation that looks for potentially problematic instructions + std::cout << "Identifying potential patches..." << std::endl; for (const auto &func : m_functions) { if (m_skipFunctions.find(func.name) != m_skipFunctions.end()) { - continue; // Skip functions that we're going to skip anyway + continue; } - // Decode the function - decodeFunction(func); - } + std::vector instructions = decodeFunction(func); - // Example: If we find syscall instructions, suggest patching them to NOP - for (uint32_t addr = 0x100000; addr < 0x101000; addr += 4) - { - if (m_elfParser->isValidAddress(addr)) + for (size_t i = 0; i < instructions.size(); i++) { - uint32_t instr = m_elfParser->readWord(addr); - if ((instr & 0xFC00003F) == 0x0000000C) - { // syscall instruction - m_patches[addr] = 0x00000000; // NOP + const auto &inst = instructions[i]; + + if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SYSCALL) + { + std::cout << "Found syscall at " << formatAddress(inst.address) << " in function " << func.name << std::endl; + m_patches[inst.address] = 0x00000000; // NOP + m_patchReasons[inst.address] = "Syscall requires special handling"; + } + + if (inst.opcode == OPCODE_COP0) + { + std::cout << "Found COP0 instruction at " << formatAddress(inst.address) << " in function " << func.name << std::endl; + m_patches[inst.address] = 0x00000000; // NOP + m_patchReasons[inst.address] = "Privileged COP0 instruction"; + } + + if (inst.opcode == OPCODE_CACHE) + { + std::cout << "Found CACHE instruction at " << formatAddress(inst.address) << " in function " << func.name << std::endl; + m_patches[inst.address] = 0x00000000; // NOP + m_patchReasons[inst.address] = "Cache manipulation not supported"; + } + + // Detect potential self-modifying code + if (inst.opcode == OPCODE_SW && i + 1 < instructions.size()) + { + const auto &nextInst = instructions[i + 1]; + + if (nextInst.opcode == OPCODE_J || nextInst.opcode == OPCODE_JAL) + { + uint32_t jumpTarget = (nextInst.address & 0xF0000000) | (nextInst.target << 2); + + for (const auto §ion : m_sections) + { + if (section.isCode && jumpTarget >= section.address && jumpTarget < section.address + section.size) + { + std::cout << "Potential self-modifying code at " << formatAddress(inst.address) << " in function " << func.name << std::endl; + m_patches[inst.address] = 0x00000000; // NOP the store + m_patchReasons[inst.address] = "Potential self-modifying code"; + } + } + } + } + + // Detect stores to regions mapped to hardware registers + if ((inst.opcode == OPCODE_SW || inst.opcode == OPCODE_SH || inst.opcode == OPCODE_SB) && + inst.rs != 28) // Not GP-relative + { + uint32_t baseAddr = 0; + for (int j = 1; j <= 5 && static_cast(inst.address) - j * 4 >= static_cast(func.start); j++) + { + uint32_t prevAddr = inst.address - j * 4; + uint32_t prevInst = m_elfParser->readWord(prevAddr); + + if (OPCODE(prevInst) == OPCODE_LUI && RT(prevInst) == inst.rs) + { + baseAddr = IMMEDIATE(prevInst) << 16; + break; + } + } + + if (baseAddr != 0) + { + uint32_t targetAddr = baseAddr + static_cast(inst.immediate); + + if ((targetAddr >= 0x10000000 && targetAddr < 0x10010000) || // Timer registers + (targetAddr >= 0x10020000 && targetAddr < 0x10030000) || // DMAC registers + (targetAddr >= 0x12000000 && targetAddr < 0x12010000)) // GS registers + { + std::cout << "Hardware register access at " << formatAddress(inst.address) + << " to address 0x" << std::hex << targetAddr << std::dec + << " in function " << func.name << std::endl; + + // We might need to replace this with a special function call but lets just patch it for now + m_patchReasons[inst.address] = "Hardware register access to " + formatAddress(targetAddr); + } + } + } + + if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SYNC) + { + std::cout << "SYNC instruction (memory barrier) at " << formatAddress(inst.address) + << " in function " << func.name << std::endl; + // We might need to add memory barriers in the recompiled code + } + + // Detect instructions that use special PS2 features like quad load/store + if (inst.opcode == OPCODE_LQ || inst.opcode == OPCODE_SQ) + { + std::cout << "Quad word " << (inst.opcode == OPCODE_LQ ? "load" : "store") + << " at " << formatAddress(inst.address) << " in function " << func.name << std::endl; + // These will require special handling with SIMD instructions } } } + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end()) + { + continue; + } + + std::vector instructions = decodeFunction(func); + + for (const auto &inst : instructions) + { + if (inst.isMMI || inst.isVU) + { + std::cout << "Found PS2 multimedia instruction at " << formatAddress(inst.address) + << " in function " << func.name << std::endl; + + // These might need special handling, but we won't patch them with NOPs + m_patchReasons[inst.address] = "PS2 multimedia instruction"; + } + } + } + } + + void ElfAnalyzer::analyzeControlFlow() + { + std::cout << "Analyzing control flow of functions..." << std::endl; + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end() || + m_libFunctions.find(func.name) != m_libFunctions.end()) + { + continue; + } + + CFG cfg = buildCFG(func); + m_functionCFGs[func.start] = cfg; + + std::vector instructions = decodeFunction(func); + for (const auto &inst : instructions) + { + if (inst.opcode == OPCODE_JAL || + (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_JALR)) + { + + uint32_t targetAddr = 0; + + if (inst.opcode == OPCODE_JAL) + { + targetAddr = (inst.address & 0xF0000000) | (inst.target << 2); + } + else + { + // For JALR, the target is in the register - harder to statically analyze so lets skip it + continue; + } + + for (const auto &targetFunc : m_functions) + { + if (targetFunc.start == targetAddr) + { + FunctionCall call; + call.callerAddress = inst.address; + call.calleeAddress = targetAddr; + call.calleeName = targetFunc.name; + + m_functionCalls[func.start].push_back(call); + + std::cout << "Function " << func.name << " calls " << targetFunc.name + << " at " << formatAddress(inst.address) << std::endl; + break; + } + } + } + } + } + } + + void ElfAnalyzer::detectJumpTables() + { + std::cout << "Detecting jump tables..." << std::endl; + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end() || + m_libFunctions.find(func.name) != m_libFunctions.end()) + { + continue; + } + + std::vector instructions = decodeFunction(func); + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (inst.opcode == OPCODE_SLTIU && i + 2 < instructions.size()) + { + const auto &nextInst = instructions[i + 1]; + if (nextInst.opcode == OPCODE_BNE || nextInst.opcode == OPCODE_BEQ) + { + for (size_t j = i + 2; j < std::min(i + 10, instructions.size()); j++) + { + const auto &loadInst = instructions[j]; + + if (loadInst.opcode == OPCODE_LW && j + 1 < instructions.size()) + { + const auto &jumpInst = instructions[j + 1]; + + if (jumpInst.opcode == OPCODE_SPECIAL && jumpInst.function == SPECIAL_JR && + jumpInst.rs == loadInst.rt) + { + std::cout << "Detected jump table in function " << func.name + << " at " << formatAddress(loadInst.address) << std::endl; + + uint32_t baseAddr = 0; + uint32_t numEntries = inst.immediate; // From the bounds check + + for (int k = j - 1; k >= static_cast(i); k--) + { + const auto &addrInst = instructions[k]; + + if (addrInst.opcode == OPCODE_LUI && k + 1 < instructions.size()) + { + const auto &offsetInst = instructions[k + 1]; + + if ((offsetInst.opcode == OPCODE_ADDIU || offsetInst.opcode == OPCODE_ORI) && + offsetInst.rs == addrInst.rt && offsetInst.rt == loadInst.rs) + { + + baseAddr = (addrInst.immediate << 16) | (offsetInst.immediate & 0xFFFF); + break; + } + } + } + + if (baseAddr != 0 && numEntries > 0 && numEntries < 1000) + { + JumpTable jumpTable; + jumpTable.address = baseAddr; + jumpTable.baseRegister = loadInst.rs; + + for (uint32_t e = 0; e < numEntries; e++) + { + uint32_t entryAddr = baseAddr + (e * 4); + + if (m_elfParser->isValidAddress(entryAddr)) + { + uint32_t targetAddr = m_elfParser->readWord(entryAddr); + + JumpTableEntry entry; + entry.index = e; + entry.target = targetAddr; + jumpTable.entries.push_back(entry); + + std::cout << " - Jump table entry " << e << ": 0x" + << std::hex << targetAddr << std::dec << std::endl; + } + } + + if (!jumpTable.entries.empty()) + { + m_jumpTables.push_back(jumpTable); + } + } + + break; + } + } + } + } + } + } + } + } + + void ElfAnalyzer::analyzePerformanceCriticalPaths() + { + std::cout << "Analyzing performance-critical paths..." << std::endl; + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end() || + m_libFunctions.find(func.name) != m_libFunctions.end()) + { + continue; + } + + std::vector instructions = decodeFunction(func); + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (inst.isBranch) + { + int32_t offset = static_cast(inst.immediate) << 2; + uint32_t targetAddr = inst.address + 4 + offset; + + if (targetAddr < inst.address) + { + size_t loopSize = (inst.address - targetAddr) / 4 + 1; + + if (loopSize < 20) + { + std::cout << "Found tight loop in function " << func.name + << " from " << formatAddress(targetAddr) + << " to " << formatAddress(inst.address) + << " (size: " << loopSize << " instructions)" << std::endl; + + bool hasMultimedia = false; + for (size_t j = 0; j < instructions.size(); j++) + { + if (instructions[j].address >= targetAddr && instructions[j].address <= inst.address) + { + if (instructions[j].isMultimedia) + { + hasMultimedia = true; + break; + } + } + } + + if (hasMultimedia) + { + std::cout << " - Loop contains multimedia instructions" << std::endl; + } + } + } + } + } + } + } + + void ElfAnalyzer::identifyRecursiveFunctions() + { + std::cout << "Identifying recursive functions..." << std::endl; + + std::unordered_map> callGraph; + + for (const auto &func : m_functions) + { + if (m_functionCalls.find(func.start) != m_functionCalls.end()) + { + for (const auto &call : m_functionCalls[func.start]) + { + callGraph[func.name].insert(call.calleeName); + } + } + } + + for (const auto &func : m_functions) + { + if (callGraph[func.name].find(func.name) != callGraph[func.name].end()) + { + std::cout << "Function " << func.name << " is directly recursive" << std::endl; + } + } + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end() || + m_libFunctions.find(func.name) != m_libFunctions.end()) + { + continue; + } + + std::set visited; + std::function detectCycle; + + detectCycle = [&](const std::string &currFunc) -> bool + { + if (visited.find(currFunc) != visited.end()) + { + return currFunc == func.name; + } + + visited.insert(currFunc); + + for (const auto &callee : callGraph[currFunc]) + { + if (detectCycle(callee)) + { + return true; + } + } + + visited.erase(currFunc); + return false; + }; + + if (detectCycle(func.name)) + { + std::cout << "Function " << func.name << " is part of a mutually recursive cycle" << std::endl; + } + } + } + + void ElfAnalyzer::analyzeRegisterUsage() + { + std::cout << "Analyzing register usage patterns..." << std::endl; + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end() || + m_libFunctions.find(func.name) != m_libFunctions.end()) + { + continue; + } + + std::vector instructions = decodeFunction(func); + std::set regsRead, regsWritten; + + for (const auto &inst : instructions) + { + if (inst.rs != 0) + regsRead.insert(inst.rs); + if (inst.rt != 0 && inst.opcode != OPCODE_SW && inst.opcode != OPCODE_SB && + inst.opcode != OPCODE_SH && inst.opcode != OPCODE_SQ) + { + regsRead.insert(inst.rt); + } + + if (inst.opcode == OPCODE_SPECIAL || inst.opcode == OPCODE_REGIMM || + inst.opcode == OPCODE_COP1 || inst.opcode == OPCODE_COP2) + { + // R-type instructions + if (inst.rd != 0) + regsWritten.insert(inst.rd); + } + else if (inst.opcode == OPCODE_JAL) + { + // JAL writes to $ra (r31) + regsWritten.insert(31); + } + else if (inst.opcode == OPCODE_LUI || inst.opcode == OPCODE_ADDIU || + inst.opcode == OPCODE_ORI || inst.opcode == OPCODE_LW || + inst.opcode == OPCODE_LB || inst.opcode == OPCODE_LH) + { + // I-type instructions that write to rt + if (inst.rt != 0) + regsWritten.insert(inst.rt); + } + } + + // Check if function follows standard calling convention + bool hasStackOps = false; + bool savesFP = false; + bool savesRA = false; + + for (size_t i = 0; i < std::min(size_t(10), instructions.size()); i++) + { + const auto &inst = instructions[i]; + + // ADDIU $sp, $sp, -X (allocate stack frame) + if (inst.opcode == OPCODE_ADDIU && inst.rs == 29 && inst.rt == 29 && + static_cast(inst.immediate) < 0) + { + hasStackOps = true; + } + + // SW $fp, X($sp) (save frame pointer) + if (inst.opcode == OPCODE_SW && inst.rt == 30 && inst.rs == 29) + { + savesFP = true; + } + + // SW $ra, X($sp) (save return address) + if (inst.opcode == OPCODE_SW && inst.rt == 31 && inst.rs == 29) + { + savesRA = true; + } + } + + if (hasStackOps) + { + std::cout << "Function " << func.name << " allocates a stack frame" << std::endl; + + if (savesFP) + std::cout << " - Saves frame pointer ($fp)" << std::endl; + if (savesRA) + std::cout << " - Saves return address ($ra)" << std::endl; + } + + if (regsRead.find(4) != regsRead.end() || regsRead.find(5) != regsRead.end() || + regsRead.find(6) != regsRead.end() || regsRead.find(7) != regsRead.end()) + { + std::cout << " - Uses argument registers (a0-a3)" << std::endl; + } + + if (regsWritten.find(2) != regsWritten.end() || regsWritten.find(3) != regsWritten.end()) + { + std::cout << " - Sets return values (v0-v1)" << std::endl; + } + } + } + + void ElfAnalyzer::analyzeFunctionSignatures() + { + std::cout << "Analyzing function signatures..." << std::endl; + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end() || + m_libFunctions.find(func.name) != m_libFunctions.end()) + { + continue; + } + + std::vector instructions = decodeFunction(func); + + int paramCount = 0; + bool usesFloatingPoint = false; + bool usesDoublewords = false; + bool returnsSomething = false; + + for (const auto &inst : instructions) + { + if ((inst.rs >= 4 && inst.rs <= 7) || (inst.rt >= 4 && inst.rt <= 7)) + { + paramCount = std::max(paramCount, static_cast(std::max(inst.rs, inst.rt) - 3)); + } + + if (inst.opcode == OPCODE_COP1) + { + usesFloatingPoint = true; + } + + // Check for 64-bit operations + if (inst.opcode == OPCODE_LD || inst.opcode == OPCODE_SD || + (inst.opcode == OPCODE_SPECIAL && + (inst.function == SPECIAL_DSLL || inst.function == SPECIAL_DSRL || + inst.function == SPECIAL_DSRA || inst.function == SPECIAL_DSLLV || + inst.function == SPECIAL_DSRLV || inst.function == SPECIAL_DSRAV))) + { + usesDoublewords = true; + } + + // Check for return value setting, addition we could do as well thous check (inst.opcode == OPCODE_ADDIU && inst.rs == 0) || // LI pattern using ADDIU $rt, $zero, imm and (inst.opcode == OPCODE_ORI && i > 0 && instructions[i - 1].opcode == OPCODE_LUI && instructions[i - 1].rt == inst.rs && inst.rt == inst.rs) + if ((inst.opcode == OPCODE_ADDIU || inst.opcode == OPCODE_ORI || + inst.opcode == OPCODE_LW) && + (inst.rt == 2 || inst.rt == 3)) + { + returnsSomething = true; + } + else if (inst.opcode == OPCODE_SPECIAL && + (inst.function == SPECIAL_ADD || inst.function == SPECIAL_ADDU || + inst.function == SPECIAL_SUB || inst.function == SPECIAL_SUBU || + inst.function == SPECIAL_AND || inst.function == SPECIAL_OR || + inst.function == SPECIAL_XOR || inst.function == SPECIAL_NOR) && + (inst.rd == 2 || inst.rd == 3)) + { + returnsSomething = true; + } + } + + if (paramCount > 0 || usesFloatingPoint || usesDoublewords || returnsSomething) + { + std::cout << "Function " << func.name << " signature analysis:" << std::endl; + if (paramCount > 0) + { + std::cout << " - Uses approximately " << paramCount << " parameter(s)" << std::endl; + } + if (usesFloatingPoint) + { + std::cout << " - Uses floating point operations" << std::endl; + } + if (usesDoublewords) + { + std::cout << " - Uses 64-bit operations" << std::endl; + } + if (returnsSomething) + { + std::cout << " - Returns a value" << std::endl; + } + } + } + } + + void ElfAnalyzer::optimizePatches() + { + std::cout << "Optimizing patches..." << std::endl; + + std::map> functionPatches; + + for (const auto &patch : m_patches) + { + uint32_t patchAddr = patch.first; + + for (const auto &func : m_functions) + { + if (patchAddr >= func.start && patchAddr < func.end) + { + functionPatches[func.start].push_back(patchAddr); + break; + } + } + } + + for (const auto &[funcStart, patchAddrs] : functionPatches) + { + auto funcIt = std::find_if(m_functions.begin(), m_functions.end(), + [funcStart](const Function &f) + { return f.start == funcStart; }); + + if (funcIt != m_functions.end()) + { + const Function &func = *funcIt; + + if (patchAddrs.size() > 3) + { + std::cout << "Function " << func.name << " has " << patchAddrs.size() + << " patches. Consider skipping or stubing instead." << std::endl; + + // If too many patches in one function, maybe better to skip it + if (patchAddrs.size() > 5 && + static_cast(patchAddrs.size()) / ((func.end - func.start) / 4) > 0.2) + { + std::cout << " - Adding " << func.name << " to skip list due to high patch density" << std::endl; + m_skipFunctions.insert(func.name); + + for (const auto &addr : patchAddrs) + { + m_patches.erase(addr); + m_patchReasons.erase(addr); + } + } + } + } + } + + std::vector patchAddrs; + for (const auto &patch : m_patches) + { + patchAddrs.push_back(patch.first); + } + + std::sort(patchAddrs.begin(), patchAddrs.end()); + + for (size_t i = 0; i < patchAddrs.size() - 1; i++) + { + if (patchAddrs[i] + 4 == patchAddrs[i + 1]) + { + std::cout << "Sequential patches at " << formatAddress(patchAddrs[i]) + << " and " << formatAddress(patchAddrs[i + 1]) << std::endl; + + // If they're both NOPs, we could potentially optimize them together + if (m_patches[patchAddrs[i]] == 0 && m_patches[patchAddrs[i + 1]] == 0) + { + std::cout << " - Both are NOPs, could be combined in recompilation" << std::endl; + } + } + } + } + + bool ElfAnalyzer::identifyMemcpyPattern(const Function &func) + { + std::vector instructions = decodeFunction(func); + + bool hasLoop = false; + bool loadsData = false; + bool storesData = false; + bool incrementsPointers = false; + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (inst.isBranch) + { + int32_t offset = static_cast(inst.immediate) << 2; + if (inst.address + 4 + offset < inst.address) + { + hasLoop = true; + } + } + + if (inst.opcode == OPCODE_LW || inst.opcode == OPCODE_LB || + inst.opcode == OPCODE_LH || inst.opcode == OPCODE_LD || + inst.opcode == OPCODE_LQ) + { + loadsData = true; + } + + if (inst.opcode == OPCODE_SW || inst.opcode == OPCODE_SB || + inst.opcode == OPCODE_SH || inst.opcode == OPCODE_SD || + inst.opcode == OPCODE_SQ) + { + storesData = true; + } + + if (inst.opcode == OPCODE_ADDIU && + (inst.immediate == 4 || inst.immediate == 8 || inst.immediate == 16)) + { + incrementsPointers = true; + } + } + + return hasLoop && loadsData && storesData && incrementsPointers; + } + + bool ElfAnalyzer::identifyMemsetPattern(const Function &func) + { + std::vector instructions = decodeFunction(func); + + bool hasLoop = false; + bool usesConstant = false; + bool storesData = false; + bool incrementsPointer = false; + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (inst.isBranch) + { + int32_t offset = static_cast(inst.immediate) << 2; + if (inst.address + 4 + offset < inst.address) + { + hasLoop = true; + } + } + + if (inst.opcode == OPCODE_LUI || inst.opcode == OPCODE_ORI || + inst.opcode == OPCODE_ADDIU || inst.opcode == OPCODE_ANDI) + { + usesConstant = true; + } + + if (inst.opcode == OPCODE_SW || inst.opcode == OPCODE_SB || + inst.opcode == OPCODE_SH || inst.opcode == OPCODE_SD || + inst.opcode == OPCODE_SQ) + { + storesData = true; + } + + if (inst.opcode == OPCODE_ADDIU && + (inst.immediate == 4 || inst.immediate == 8 || inst.immediate == 16)) + { + incrementsPointer = true; + } + } + + return hasLoop && usesConstant && storesData && incrementsPointer; + } + + bool ElfAnalyzer::identifyStringOperationPattern(const Function &func) + { + std::vector instructions = decodeFunction(func); + + bool hasLoop = false; + bool checksZero = false; + bool loadsByte = false; + bool storesByte = false; + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (inst.isBranch) + { + int32_t offset = static_cast(inst.immediate) << 2; + if (inst.address + 4 + offset < inst.address) + { + hasLoop = true; + } + } + + if ((inst.opcode == OPCODE_BEQ && (inst.rs == 0 || inst.rt == 0)) || + (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SLT && inst.rd != 0)) + { + checksZero = true; + } + + if (inst.opcode == OPCODE_LB || inst.opcode == OPCODE_LBU) + { + loadsByte = true; + } + + if (inst.opcode == OPCODE_SB) + { + storesByte = true; + } + } + + return hasLoop && checksZero && (loadsByte || storesByte); + } + + bool ElfAnalyzer::identifyMathPattern(const Function &func) + { + std::vector instructions = decodeFunction(func); + + int mathOps = 0; + bool usesFPU = false; + + for (const auto &inst : instructions) + { + // Count ALU operations + if (inst.opcode == OPCODE_SPECIAL && + (inst.function == SPECIAL_ADD || inst.function == SPECIAL_ADDU || + inst.function == SPECIAL_SUB || inst.function == SPECIAL_SUBU || + inst.function == SPECIAL_MULT || inst.function == SPECIAL_MULTU || + inst.function == SPECIAL_DIV || inst.function == SPECIAL_DIVU)) + { + mathOps++; + } + + // Check for FPU usage + if (inst.opcode == OPCODE_COP1) + { + usesFPU = true; + mathOps++; + } + } + + // If more than 30% of instructions are math operations, it's likely a math function + return mathOps > instructions.size() * 0.3 || usesFPU; + } + + CFG ElfAnalyzer::buildCFG(const Function &function) + { + CFG cfg; + std::vector instructions = decodeFunction(function); + std::map addrToIndex; + + for (size_t i = 0; i < instructions.size(); i++) + { + addrToIndex[instructions[i].address] = i; + } + + std::set leaders = {function.start}; // Entry point is always a leader + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (inst.isBranch || inst.isJump) + { + if (i + 1 < instructions.size()) + { + leaders.insert(instructions[i + 1].address); + } + + if (inst.isBranch) + { + int32_t offset = static_cast(inst.immediate) << 2; + uint32_t target = inst.address + 4 + offset; + leaders.insert(target); + } + + // Jump target for J/JAL + if ((inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL) && !inst.isCall) + { + uint32_t target = (inst.address & 0xF0000000) | (inst.target << 2); + leaders.insert(target); + } + } + } + + uint32_t currentLeader = 0; + CFGNode currentNode; + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (leaders.find(inst.address) != leaders.end()) + { + if (currentLeader != 0) + { + currentNode.endAddress = instructions[i - 1].address; + cfg[currentLeader] = currentNode; + } + + currentLeader = inst.address; + currentNode = CFGNode(); + currentNode.startAddress = currentLeader; + currentNode.isJumpTarget = true; + currentNode.instructions.clear(); + } + + currentNode.instructions.push_back(inst); + + if (i == instructions.size() - 1) + { + currentNode.endAddress = inst.address; + cfg[currentLeader] = currentNode; + } + } + + for (auto &[addr, node] : cfg) + { + const auto &lastInst = node.instructions.back(); + + if (lastInst.isBranch) + { + int32_t offset = static_cast(lastInst.immediate) << 2; + uint32_t targetAddr = lastInst.address + 4 + offset; + + if (cfg.find(targetAddr) != cfg.end()) + { + node.successors.push_back(targetAddr); + cfg[targetAddr].predecessors.push_back(addr); + } + + bool likelyBranch = (lastInst.opcode == OPCODE_BEQL || + lastInst.opcode == OPCODE_BNEL || + lastInst.opcode == OPCODE_BLEZL || + lastInst.opcode == OPCODE_BGTZL); + + if (!likelyBranch) + { + if (lastInst.address + 8 <= function.end) + { + uint32_t nextAddr = lastInst.address + 8; // Skip delay slot + + for (const auto &[blockAddr, blockNode] : cfg) + { + if (blockAddr == nextAddr || + (nextAddr > blockAddr && nextAddr <= blockNode.endAddress)) + { + node.successors.push_back(blockAddr); + cfg[blockAddr].predecessors.push_back(addr); + break; + } + } + } + } + } + else if (lastInst.isJump) + { + if (lastInst.opcode == OPCODE_J || lastInst.opcode == OPCODE_JAL) + { + // Direct jump + uint32_t targetAddr = (lastInst.address & 0xF0000000) | (lastInst.target << 2); + + // Only add successor if it's within this function + if (targetAddr >= function.start && targetAddr < function.end && + cfg.find(targetAddr) != cfg.end()) + { + node.successors.push_back(targetAddr); + cfg[targetAddr].predecessors.push_back(addr); + } + } + // We don't handle indirect jumps (JR/JALR) statically + } + else if (!lastInst.isReturn) + { + if (lastInst.address + 4 <= function.end) + { + uint32_t nextAddr = lastInst.address + 4; + + for (const auto &[blockAddr, blockNode] : cfg) + { + if (blockAddr == nextAddr) + { + node.successors.push_back(blockAddr); + cfg[blockAddr].predecessors.push_back(addr); + break; + } + } + } + } + } + + return cfg; } std::string ElfAnalyzer::escapeBackslashes(const std::string &path) @@ -277,9 +1620,18 @@ namespace ps2recomp static const std::unordered_set systemFuncs = { "entry", "_start", "_init", "_fini", "abort", "exit", "_exit", - "_profiler_start", "_profiler_stop"}; + "_profiler_start", "_profiler_stop", + "__main", "__do_global_ctors", "__do_global_dtors", + "_GLOBAL__sub_I_", "_GLOBAL__sub_D_", + "__ctor_list", "__dtor_list", "_edata", "_end", + "etext", "__exidx_start", "__exidx_end", + "_ftext", "__bss_start", "__bss_start__", + "__bss_end__", "__end__", "_stack", "_dso_handle"}; - return systemFuncs.find(name) != systemFuncs.end(); + return systemFuncs.find(name) != systemFuncs.end() || + name.find("__") == 0 || + name.find("_Z") == 0 || // C++ mangled names + name.find(".") == 0; // .text.* or .plt.* symbols } bool ElfAnalyzer::isLibraryFunction(const std::string &name) const @@ -292,14 +1644,17 @@ namespace ps2recomp return true; // Many library functions start with underscore } - // Check for common prefixes by Claude const std::vector libraryPrefixes = { - "sce", "Sce", "SCE", // Sony prefixes - "sif", "Sif", "SIF", // SIF functions - "pad", "Pad", "PAD", // Pad functions - "gs", "Gs", "GS", // Graphics Synthesizer - "dma", "Dma", "DMA", // DMA functions - "iop", "Iop", "IOP" // IOP functions + "sce", "Sce", "SCE", // Sony prefixes + "sif", "Sif", "SIF", // SIF functions + "pad", "Pad", "PAD", // Pad functions + "gs", "Gs", "GS", // Graphics Synthesizer + "dma", "Dma", "DMA", // DMA functions + "iop", "Iop", "IOP", // IOP functions + "vif", "Vif", "VIF", // VIF functions + "spu", "Spu", "SPU", // SPU functions + "mc", "Mc", "MC", // Memory Card functions + "libc", "Libc", "LIBC" // C library functions }; for (const auto &prefix : libraryPrefixes) @@ -310,31 +1665,33 @@ namespace ps2recomp } } + // Check for common C/C++ library function names + static const std::regex cLibPattern("^(mem|str|time|f?printf|f?scanf|malloc|free|calloc|realloc|atoi|itoa|rand|srand|abort|exit|atexit|getenv|system|bsearch|qsort|abs|labs|div|ldiv|mblen|mbtowc|wctomb|mbstowcs|wcstombs).*"); + if (std::regex_match(name, cLibPattern)) + { + return true; + } + return false; } - void ElfAnalyzer::decodeFunction(const Function &function) + std::vector ElfAnalyzer::decodeFunction(const Function &function) { + std::vector instructions; + for (uint32_t addr = function.start; addr < function.end; addr += 4) { if (!m_elfParser->isValidAddress(addr)) { continue; } - + uint32_t rawInstruction = m_elfParser->readWord(addr); try { Instruction inst = m_decoder->decodeInstruction(addr, rawInstruction); - - // TODO Analyze instructions for potential issues - - // Example: Look for syscalls that might need to be patched - if (inst.opcode == 0 && inst.function == 0xC) - { // syscall - std::cout << "Found syscall at " << formatAddress(addr) << std::endl; - } + instructions.push_back(inst); } catch (const std::exception &e) { @@ -342,6 +1699,8 @@ namespace ps2recomp << ": " << e.what() << std::endl; } } + + return instructions; } std::string ElfAnalyzer::formatAddress(uint32_t address) const @@ -351,4 +1710,188 @@ namespace ps2recomp return ss.str(); } + bool ElfAnalyzer::hasMMIInstructions(const Function &function) + { + std::vector instructions = decodeFunction(function); + + for (const auto &inst : instructions) + { + if (inst.isMMI || inst.opcode == OPCODE_MMI) + { + return true; + } + } + + return false; + } + + bool ElfAnalyzer::hasVUInstructions(const Function &function) + { + std::vector instructions = decodeFunction(function); + + for (const auto &inst : instructions) + { + if (inst.isVU || inst.opcode == OPCODE_COP2) + { + return true; + } + } + + return false; + } + + bool ElfAnalyzer::identifyFunctionType(const Function &function) + { + if (m_libFunctions.find(function.name) != m_libFunctions.end() || + m_skipFunctions.find(function.name) != m_skipFunctions.end()) + { + return false; + } + + std::vector instructions = decodeFunction(function); + + bool hasHardwareIO = false; + bool hasComplexMMI = false; + bool isVeryLarge = instructions.size() > 500; // Arbitrary large function threshold + + for (const auto &inst : instructions) + { + // Check for LUI+SW combinations to hardware registers + if (inst.opcode == OPCODE_LUI) + { + uint32_t upperAddr = inst.immediate << 16; + + // Check if upper address is in hardware region + if ((upperAddr >= 0x10000000 && upperAddr < 0x14000000) || // I/O area + (upperAddr >= 0x1F800000 && upperAddr < 0x1F900000)) // Scratchpad RAM + { + hasHardwareIO = true; + } + } + + // Check for complex MMI operations + if (inst.isMMI && + (inst.opcode == OPCODE_MMI && + (inst.function == MMI_MMI0 || inst.function == MMI_MMI1 || + inst.function == MMI_MMI2 || inst.function == MMI_MMI3))) + { + hasComplexMMI = true; + } + } + + if (hasHardwareIO) + { + m_skipFunctions.insert(function.name); + std::cout << "Skipping function " << function.name << " due to hardware I/O" << std::endl; + return true; + } + else if (hasComplexMMI && isVeryLarge) + { + m_skipFunctions.insert(function.name); + std::cout << "Skipping large function " << function.name << " with complex MMI" << std::endl; + return true; + } + + return false; + } + + void ElfAnalyzer::categorizeFunction(Function &function) + { + identifyFunctionType(function); + + if (isSelfModifyingCode(function)) + { + std::cout << "Function " << function.name << " contains self-modifying code" << std::endl; + m_skipFunctions.insert(function.name); + } + + if (isLoopHeavyFunction(function)) + { + std::cout << "Function " << function.name << " is loop-heavy, may need optimization" << std::endl; + } + } + + bool ElfAnalyzer::isSelfModifyingCode(const Function &function) + { + std::vector instructions = decodeFunction(function); + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if ((inst.opcode == OPCODE_SW || inst.opcode == OPCODE_SH || + inst.opcode == OPCODE_SB || inst.opcode == OPCODE_SQ)) + { + + uint32_t baseAddr = 0; + + // Look for preceding LUI instruction + for (int j = i - 1; j >= 0 && j >= static_cast(i) - 5; j--) + { + const auto &prevInst = instructions[j]; + + if (prevInst.opcode == OPCODE_LUI && prevInst.rt == inst.rs) + { + baseAddr = prevInst.immediate << 16; + break; + } + } + + if (baseAddr != 0) + { + uint32_t targetAddr = baseAddr + static_cast(inst.immediate); + + // Check if target address is within a code section + for (const auto §ion : m_sections) + { + if (section.isCode && targetAddr >= section.address && + targetAddr < section.address + section.size) + { + return true; + } + } + } + } + } + + return false; + } + + bool ElfAnalyzer::isLoopHeavyFunction(const Function &function) + { + std::vector instructions = decodeFunction(function); + int loopCount = 0; + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (inst.isBranch) + { + int32_t offset = static_cast(inst.immediate) << 2; + if (offset < 0) + { + loopCount++; + } + } + } + + // Consider it loop-heavy if it has more than 3 loops + return loopCount > 3; + } + + uint32_t ElfAnalyzer::getSuccessor(const Instruction &inst, uint32_t currentAddr) + { + if (inst.isBranch) + { + int32_t offset = static_cast(inst.immediate) << 2; + return currentAddr + 4 + offset; + } + else if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL) + { + return (currentAddr & 0xF0000000) | (inst.target << 2); + } + + return currentAddr + 4; + } } \ No newline at end of file