#include "psprecomp/common.hpp" #include "psprecomp/elf32.hpp" #include "psprecomp/nid_registry.hpp" #include "psprecomp/program_analysis.hpp" #include "psprecomp/sha256.hpp" #include #include #include #include namespace { std::string json_escape(const std::string &s) { std::string out; for (char c : s) { switch (c) { case '\\': out += "\\\\"; break; case '"': out += "\\\""; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: out += c; break; } } return out; } void write_import_csv(const std::filesystem::path &path, const std::vector &imports, const psprecomp::NidRegistry &nids) { std::ofstream out(path); if (!out) throw psprecomp::Error("Cannot create import CSV: " + path.string()); out << "library,nid,name,stub_address\n"; for (const auto &imp : imports) { out << imp.library << ',' << psprecomp::hex32(imp.nid) << ',' << nids.resolve(imp.library, imp.nid).value_or("") << ',' << psprecomp::hex32(imp.stub_address) << '\n'; } } void write_seed_csv(const std::filesystem::path &path, const std::map &seeds, std::uint32_t entry) { std::ofstream out(path); if (!out) throw psprecomp::Error("Cannot create function seed CSV: " + path.string()); out << "name,address,source\n"; for (const auto &[address, source] : seeds) { out << (address == entry ? "module_start" : "sub_" + psprecomp::hex32(address).substr(2)) << ',' << psprecomp::hex32(address) << ',' << source << '\n'; } } void write_function_csv(const std::filesystem::path &path, const psprecomp::ProgramAnalysis &program) { std::ofstream out(path); if (!out) throw psprecomp::Error("Cannot create automatic function CSV: " + path.string()); out << "name,address,instructions,basic_blocks,direct_calls,indirect_call_sites,unsupported,truncated,seed_source\n"; for (const auto &function : program.functions) { const auto source = program.seeds.find(function.entry); out << "sub_" << psprecomp::hex32(function.entry).substr(2) << ',' << psprecomp::hex32(function.entry) << ',' << function.labels.size() << ',' << function.basic_block_count << ',' << function.direct_calls.size() << ',' << function.indirect_call_sites.size() << ',' << function.unsupported_instruction_count << ',' << (function.truncated ? "true" : "false") << ',' << (source != program.seeds.end() ? source->second : "") << '\n'; } } } int main(int argc, char **argv) { try { if (argc < 2 || argc > 4) { std::cerr << "Usage: psp_analyze [report.json] [load_base_hex]\n"; return 2; } const std::filesystem::path input = argv[1]; const std::filesystem::path output = argc >= 3 ? argv[2] : "psp_report.json"; std::uint32_t load_base = psprecomp::kDefaultPspUserLoadBase; if (argc == 4) load_base = static_cast(std::stoul(argv[3], nullptr, 0)); auto elf = psprecomp::Elf32Image::from_file(input); psprecomp::GuestMemory memory(32u * 1024u * 1024u); const auto relocations = elf.load_and_relocate(memory, load_base); const auto module = elf.find_module_info(memory, load_base); psprecomp::NidRegistry nids; std::vector imports; if (module) imports = elf.scan_imports(memory, *module); const auto program = psprecomp::analyze_program(elf, memory, load_base); std::filesystem::create_directories(output.parent_path().empty() ? std::filesystem::path(".") : output.parent_path()); const auto stem = output.parent_path() / output.stem(); const auto imports_csv = std::filesystem::path(stem.string() + "_imports.csv"); const auto seeds_csv = std::filesystem::path(stem.string() + "_function_seeds.csv"); const auto functions_csv = std::filesystem::path(stem.string() + "_functions_auto.csv"); write_import_csv(imports_csv, imports, nids); write_seed_csv(seeds_csv, program.seeds, elf.runtime_entry(load_base)); write_function_csv(functions_csv, program); std::map import_counts; for (const auto &imp : imports) ++import_counts[imp.library]; std::size_t total_blocks = 0u; std::size_t total_indirect_sites = 0u; std::size_t total_unsupported = 0u; std::size_t truncated_functions = 0u; for (const auto &function : program.functions) { total_blocks += function.basic_block_count; total_indirect_sites += function.indirect_call_sites.size(); total_unsupported += function.unsupported_instruction_count; truncated_functions += function.truncated ? 1u : 0u; } std::ofstream out(output); if (!out) throw psprecomp::Error("Cannot create report: " + output.string()); out << "{\n" << " \"input\": \"" << json_escape(input.string()) << "\",\n" << " \"sha256\": \"" << psprecomp::sha256_file(input) << "\",\n" << " \"elf_type\": " << elf.type() << ",\n" << " \"is_psp_prx\": " << (elf.is_psp_prx() ? "true" : "false") << ",\n" << " \"load_base\": \"" << psprecomp::hex32(load_base) << "\",\n" << " \"entry_relative\": \"" << psprecomp::hex32(elf.entry()) << "\",\n" << " \"entry_runtime\": \"" << psprecomp::hex32(elf.runtime_entry(load_base)) << "\",\n" << " \"relocations\": {\"total\":" << relocations.total << ",\"r_mips_32\":" << relocations.r_mips_32 << ",\"r_mips_26\":" << relocations.r_mips_26 << ",\"r_mips_hi16\":" << relocations.r_mips_hi16 << ",\"r_mips_lo16\":" << relocations.r_mips_lo16 << ",\"unsupported\":" << relocations.unsupported << ",\"invalid\":" << relocations.invalid << "},\n" << " \"segments\": [\n"; for (std::size_t i = 0; i < elf.segments().size(); ++i) { const auto &s = elf.segments()[i]; out << " {\"index\":" << i << ",\"type\":" << s.type << ",\"vaddr_relative\":\"" << psprecomp::hex32(s.vaddr) << "\",\"vaddr_runtime\":\"" << psprecomp::hex32(elf.segment_runtime_address(i, load_base)) << "\",\"filesz\":" << s.file_size << ",\"memsz\":" << s.memory_size << ",\"flags\":" << s.flags << "}" << (i + 1 < elf.segments().size() ? "," : "") << "\n"; } out << " ],\n \"module\": "; if (module) { out << "{\"name\":\"" << json_escape(module->name) << "\",\"attributes\":" << module->attributes << ",\"version\":\"" << static_cast(module->major_version) << '.' << static_cast(module->minor_version) << "\",\"address\":\"" << psprecomp::hex32(module->address) << "\",\"gp\":\"" << psprecomp::hex32(module->gp) << "\",\"stub_top\":\"" << psprecomp::hex32(module->stub_top) << "\",\"stub_end\":\"" << psprecomp::hex32(module->stub_end) << "\"}"; } else { out << "null"; } out << ",\n \"import_count\": " << imports.size() << ",\n" << " \"import_libraries\": {\n"; std::size_t library_index = 0; for (const auto &[library, count] : import_counts) { out << " \"" << json_escape(library) << "\": " << count << (++library_index < import_counts.size() ? "," : "") << "\n"; } out << " },\n" << " \"function_seed_count\": " << program.seeds.size() << ",\n" << " \"automatic_analysis\": {\n" << " \"function_count\": " << program.functions.size() << ",\n" << " \"unique_instruction_labels\": " << program.covered_labels.size() << ",\n" << " \"unique_block_entries\": " << program.covered_entry_labels.size() << ",\n" << " \"basic_block_count_with_overlap\": " << total_blocks << ",\n" << " \"indirect_call_sites\": " << total_indirect_sites << ",\n" << " \"unsupported_instruction_occurrences\": " << total_unsupported << ",\n" << " \"overlapping_label_occurrences\": " << program.overlapping_label_count << ",\n" << " \"truncated_functions\": " << truncated_functions << "\n" << " },\n" << " \"artifacts\": {\"imports_csv\":\"" << json_escape(imports_csv.string()) << "\",\"function_seeds_csv\":\"" << json_escape(seeds_csv.string()) << "\",\"automatic_functions_csv\":\"" << json_escape(functions_csv.string()) << "\"}\n" << "}\n"; std::cout << "Analyzed " << input.string() << "\n" << " runtime entry: " << psprecomp::hex32(elf.runtime_entry(load_base)) << "\n" << " relocations: " << relocations.total << " (invalid " << relocations.invalid << ", unsupported " << relocations.unsupported << ")\n" << " module: " << (module ? module->name : "") << "\n" << " imports: " << imports.size() << "\n" << " automatic functions: " << program.functions.size() << "\n" << " unique code labels: " << program.covered_labels.size() << "\n" << " unique block entries:" << program.covered_entry_labels.size() << "\n" << " blocks with overlap: " << total_blocks << "\n" << " indirect call sites: " << total_indirect_sites << "\n" << "Wrote " << output.string() << "\n"; return 0; } catch (const std::exception &e) { std::cerr << "psp_analyze error: " << e.what() << "\n"; return 1; } }