#include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #endif static void Require(bool value, const char* message) { if (value) return; std::fprintf(stderr, "%s\n", message); std::exit(1); } template using Prefix = int (APS5_VABI*)(const TChar**, TChar**); struct Case { const char* text; int code; std::size_t cursor; bool writesEnd; }; constexpr std::array Cases{ Case{"", 1, 0, false}, Case{"12.5e4", 1, 0, false}, Case{".", 1, 0, false}, Case{"-", 9, 1, false}, Case{"+", 1, 1, false}, Case{" -12", 9, 3, false}, Case{"\t\r\n\v\f +0x1p2", 2, 9, false}, Case{"0x1", 2, 2, false}, Case{"-0X.8p2", 10, 3, false}, Case{"0x", 1, 0, false}, Case{"0x.", 1, 0, false}, Case{"0xg", 1, 0, false}, Case{"0x.1", 2, 2, false}, Case{"0x..1", 1, 0, false}, Case{"0x 1", 1, 0, false}, Case{" -0xg", 9, 3, false}, Case{"n", 0, 0, true}, Case{"na", 0, 0, true}, Case{"nax", 0, 0, true}, Case{" -Na", 0, 0, true}, Case{"nan", 4, 3, true}, Case{"-NaN", 4, 4, true}, Case{"nan()", 4, 5, true}, Case{"nan(a_Z09)", 4, 10, true}, Case{"nan(payload", 4, 3, true}, Case{"nan(a-b)", 4, 3, true}, Case{"nan(a(b))", 4, 3, true}, Case{"nan()tail", 4, 5, true}, Case{"i", 0, 0, true}, Case{"in", 0, 0, true}, Case{"inx", 0, 0, true}, Case{" -iN", 0, 0, true}, Case{"inf", 3, 3, true}, Case{"-INF", 11, 4, true}, Case{"infinity", 3, 8, true}, Case{"+InFiNiTy!", 3, 9, true}, Case{"infi", 3, 3, true}, Case{"infin", 3, 3, true}, Case{"infinit", 3, 3, true}, Case{"infinite", 3, 3, true}, Case{" -infinity", 11, 11, true}, Case{"not-a-number", 0, 0, true}, Case{"garbage", 1, 0, false}, }; template static std::vector Text(const char* source) { std::vector result; do result.push_back(static_cast(*source)); while (*source++); return result; } template static void CheckOne(Prefix function, const TChar* text, const Case& test) { constexpr std::uint64_t guard = 0xd3c4b5a69788796a; TChar sentinel{}; struct Outputs { std::uint64_t before; const TChar* cursor; std::uint64_t middle; TChar* end; std::uint64_t after; }; static_assert(offsetof(Outputs, cursor) == 8 && offsetof(Outputs, middle) == 16); static_assert(offsetof(Outputs, end) == 24 && offsetof(Outputs, after) == 32); Outputs outputs{guard, text, guard, &sentinel, guard}; const int code = function(&outputs.cursor, &outputs.end); if (code != test.code || outputs.cursor != text + test.cursor || outputs.end != (test.writesEnd ? text + test.cursor : &sentinel)) { std::fprintf(stderr, "prefix '%s': code %d expected %d, cursor %td expected %zu\n", test.text, code, test.code, outputs.cursor - text, test.cursor); std::exit(1); } Require(outputs.before == guard && outputs.middle == guard && outputs.after == guard, "output bounds"); const TChar* cursor = text; Require(function(&cursor, nullptr) == test.code && cursor == text + test.cursor, "optional end pointer"); } template static void Check(Prefix function, unsigned payloadLimit = 256) { for (const auto& test : Cases) { auto text = Text(test.text); const auto saved = text; CheckOne(function, text.data(), test); Require(text == saved, "input modified"); } for (const char* word : {"nan", "inf", "infinity"}) { const std::size_t length = std::strlen(word); for (unsigned mask = 0; mask < (1u << length); ++mask) { std::string text = word; for (std::size_t index = 0; index < length; ++index) if (mask & (1u << index)) text[index] -= 'a' - 'A'; for (const char* sign : {"", "+", "-"}) { const std::string input = " \t" + std::string(sign) + text; const int code = word[0] == 'n' ? 4 : 3 + (sign[0] == '-' ? 8 : 0); const Case test{input.c_str(), code, input.size(), true}; const auto data = Text(input.c_str()); CheckOne(function, data.data(), test); } } } for (unsigned value = 0; value < payloadLimit; ++value) { const std::array payload{TChar{'n'}, TChar{'a'}, TChar{'n'}, TChar{'('}, static_cast(value), TChar{')'}, TChar{}}; const bool accepted = (value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z') || (value >= '0' && value <= '9') || value == '_'; const std::size_t stop = accepted ? 6 : value == ')' ? 5 : 3; const std::string label = "payload byte " + std::to_string(value) + ", unit width " + std::to_string(sizeof(TChar)); CheckOne(function, payload.data(), Case{label.c_str(), 4, stop, true}); } if constexpr (sizeof(TChar) == 2) { const std::array high{static_cast(0x100), TChar{'i'}, TChar{'n'}, TChar{}}; CheckOne(function, high.data(), Case{"wide code unit", 1, 0, false}); } #ifdef _WIN32 SYSTEM_INFO info{}; GetSystemInfo(&info); const std::size_t page = info.dwPageSize; auto* allocation = static_cast(VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Require(allocation != nullptr, "guard allocation"); DWORD previous = 0; Require(VirtualProtect(allocation + page, page, PAGE_NOACCESS, &previous) != 0, "guard protection"); #else const std::size_t page = static_cast(sysconf(_SC_PAGESIZE)); auto* allocation = static_cast(mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); Require(allocation != MAP_FAILED, "guard allocation"); Require(mprotect(allocation + page, page, PROT_NONE) == 0, "guard protection"); #endif for (const auto& test : Cases) { const auto source = Text(test.text); const std::size_t bytes = source.size() * sizeof(TChar); auto* text = reinterpret_cast(allocation + page - bytes); std::memcpy(text, source.data(), bytes); CheckOne(function, text, test); } #ifdef _WIN32 Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0, "guard release"); #else Require(munmap(allocation, page * 2) == 0, "guard release"); #endif } template static void CheckConcurrent(Prefix function) { std::array workers; for (auto& worker : workers) { worker = std::thread([function] { for (unsigned repeat = 0; repeat < 8; ++repeat) { for (const auto& test : Cases) { const auto text = Text(test.text); CheckOne(function, text.data(), test); } } }); } for (auto& worker : workers) worker.join(); } int main(int argc, char** argv) { Require(argc == 4, "library directory, patched/unpatched, narrow/wide"); const bool wide = std::string(argv[3]) == "wide"; Require(wide || std::string(argv[3]) == "narrow", "width selection"); const std::string name = wide ? "_WStopfx" : "_Stopfx"; const std::string symbol = std::string(argv[2]) == "patched" ? Nid::ComputeNid(name, "") : name + "_nid_postfix"; const auto path = std::filesystem::absolute(argv[1]) / "libc.prx"; #ifdef _WIN32 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); const auto startup = GetModuleHandleW(L"libc.prx"); const auto library = LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); Require(library != nullptr, "load libc"); std::array loaded{}; const auto length = GetModuleFileNameW(library, loaded.data(), static_cast(loaded.size())); Require(length != 0 && length < loaded.size() && std::filesystem::equivalent(path, std::filesystem::path(loaded.data())), "wrong libc loaded"); if (startup) Require(startup == library, "startup libc differs"); const auto function = reinterpret_cast(GetProcAddress(library, symbol.c_str())); #else const auto library = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); if (!library) std::fprintf(stderr, "%s\n", dlerror()); Require(library != nullptr, "load libc"); const auto function = dlsym(library, symbol.c_str()); #endif if (!function) std::fprintf(stderr, "Missing %s (%s)\n", name.c_str(), symbol.c_str()); Require(function != nullptr, "cannot exercise missing prefix parser"); if (wide) { const auto prefix = reinterpret_cast>(function); Check(prefix); CheckConcurrent(prefix); } else { const auto prefix = reinterpret_cast>(function); Check(prefix); CheckConcurrent(prefix); } std::printf("%s: prefix codes, pointer updates, input/output bounds and guarded reads passed\n", name.c_str()); return 0; }