AnyPS5/core/libs/tests/GuestMessageDestructorAbi.cpp

231 lines
11 KiB
C++

#include "prx/libc/include/exceptions/Runtime.hpp"
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <regex>
#include <windows.h>
using GuestWhat = const char* (APS5_VABI *)(const void*);
using GuestDestroy = void (APS5_VABI *)(void*);
using HostDestroy = void (*)(void*);
using GuestThrowFunction = void (APS5_VABI *)(std::uintptr_t);
struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; };
struct Object { const void* vtable; const char* message; };
struct Table { std::ptrdiff_t offset; const TypeRecord* type; GuestDestroy destroy; GuestDestroy deleteObject; GuestWhat what; };
struct Message { std::size_t length; std::size_t capacity; std::atomic<std::ptrdiff_t> references; };
static_assert(sizeof(Object) == 16);
extern "C" {
extern const unsigned char _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix[];
extern const unsigned char _ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix[];
extern const unsigned char _ZTISt12out_of_range_nid_postfix[];
extern const unsigned char _ZTISt11regex_error_nid_postfix[];
void APS5_VABI _ZNSt12out_of_rangeC1EPKc_nid_postfix(Object*, const char*);
void APS5_VABI _ZNSt12out_of_rangeC1ERKS__nid_postfix(Object*, const Object*);
void APS5_VABI _ZNSt12out_of_rangeD1Ev_nid_postfix(Object*);
[[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char*);
[[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type);
int APS5_VABI GuestOuter();
void APS5_VABI GuestStdCapture();
void APS5_VABI ProbeDestroy(GuestDestroy, void*, void*);
const char* APS5_VABI ProbeWhat(GuestWhat, const void*, const void*);
void APS5_VABI _ZNSt8bad_castC1Ev_nid_postfix(Object*);
void APS5_VABI _ZNSt9bad_allocC1Ev_nid_postfix(Object*);
TypeRecord FixtureBaseType{_ZTVN10__cxxabiv117__class_type_infoE_nid_postfix + 16, "11FixtureBase", nullptr};
TypeRecord FixtureErrorType{_ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix + 16, "12FixtureError", &FixtureBaseType};
TypeRecord FixtureOtherType{_ZTVN10__cxxabiv117__class_type_infoE_nid_postfix + 16, "12FixtureOther", nullptr};
const void* FixtureStdOutType = _ZTISt12out_of_range_nid_postfix;
GuestThrowFunction FixtureThrow = reinterpret_cast<GuestThrowFunction>(_ZSt14_Xout_of_rangePKc_nid_postfix);
std::uintptr_t FixtureArgument;
extern const char FixtureMessage[] = "own System-V guest message";
void* FixtureLastObject = nullptr;
unsigned FixtureDestroyed = 0;
unsigned FixtureGuardDestroyed = 0;
unsigned FixtureCaught = 0;
unsigned FixtureWhatChecked = 0;
[[noreturn]] void APS5_VABI FixtureFail(unsigned code) {
std::fprintf(stderr, "guest exception failure %u\n", code);
ExitProcess(code);
}
const char* APS5_VABI FixtureWhat(const void* object) { return static_cast<const Object*>(object)->message; }
void APS5_VABI FixtureDestroy(void* object) {
if (object != FixtureLastObject) FixtureFail(11);
++FixtureDestroyed;
}
Table FixtureVtable{0, &FixtureErrorType, FixtureDestroy, FixtureDestroy, FixtureWhat};
void APS5_VABI FixtureCheckWhat(const char* message) {
if (!message || std::strcmp(message, FixtureMessage)) FixtureFail(12);
++FixtureWhatChecked;
}
}
static Object decoy{}, retained{};
static GuestDestroy registeredDestructor;
static void* expectedObject;
static void* poison;
static unsigned callbacks;
static bool regularRelease;
static bool keepMessage;
static const char* expectedMessage = FixtureMessage;
using NativeFree = void (*)(void*);
static NativeFree originalFree;
static std::uintptr_t* freeSlot;
static void* watchedObject;
static void* watchedMessage;
static void* foreignObject;
static void* foreignMessage;
static unsigned objectFrees, messageFrees;
static void TrackedFree(void* pointer) {
if (pointer && (pointer == foreignObject || pointer == foreignMessage)) FixtureFail(50);
if (pointer && pointer == watchedObject && ++objectFrees != 1) FixtureFail(51);
if (pointer && pointer == watchedMessage && ++messageFrees != 1) FixtureFail(52);
originalFree(pointer);
}
static void ReplaceFree(std::uintptr_t address) {
DWORD protection;
if (!VirtualProtect(freeSlot, sizeof(*freeSlot), PAGE_READWRITE, &protection)) FixtureFail(53);
*freeSlot = address;
DWORD ignored;
if (!VirtualProtect(freeSlot, sizeof(*freeSlot), protection, &ignored)) FixtureFail(54);
}
static void TrackFree() {
auto* image = reinterpret_cast<unsigned char*>(GetModuleHandleW(L"libc.prx"));
if (!image) FixtureFail(55);
auto* dos = reinterpret_cast<IMAGE_DOS_HEADER*>(image);
auto* nt = reinterpret_cast<IMAGE_NT_HEADERS64*>(image + dos->e_lfanew);
auto* imports = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>(image +
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
for (; imports->Name; ++imports) {
if (!imports->OriginalFirstThunk) continue;
auto* names = reinterpret_cast<IMAGE_THUNK_DATA64*>(image + imports->OriginalFirstThunk);
auto* slots = reinterpret_cast<IMAGE_THUNK_DATA64*>(image + imports->FirstThunk);
for (; names->u1.AddressOfData; ++names, ++slots) {
if (IMAGE_SNAP_BY_ORDINAL64(names->u1.Ordinal)) continue;
auto* name = reinterpret_cast<IMAGE_IMPORT_BY_NAME*>(image + names->u1.AddressOfData);
if (std::strcmp(name->Name, "free")) continue;
freeSlot = reinterpret_cast<std::uintptr_t*>(&slots->u1.Function);
originalFree = reinterpret_cast<NativeFree>(*freeSlot);
ReplaceFree(reinterpret_cast<std::uintptr_t>(TrackedFree));
return;
}
}
FixtureFail(56);
}
static int TestVtable(const char* mode) {
const bool plain = std::strstr(mode, "plain") != nullptr;
const bool deleting = std::strstr(mode, "delete") != nullptr;
const bool destroying = deleting || std::strstr(mode, "destroy") != nullptr;
auto* actual = static_cast<Object*>(std::malloc(sizeof(Object)));
auto* other = static_cast<Object*>(std::malloc(sizeof(Object)));
if (!actual || !other) FixtureFail(57);
actual->message = other->message = nullptr;
if (plain) {
_ZNSt9bad_allocC1Ev_nid_postfix(actual);
_ZNSt8bad_castC1Ev_nid_postfix(other);
} else {
_ZNSt12out_of_rangeC1EPKc_nid_postfix(actual, "actual virtual message");
_ZNSt12out_of_rangeC1EPKc_nid_postfix(other, "unrelated virtual message");
}
const Object originalOther = *other;
watchedObject = actual;
watchedMessage = plain ? nullptr : reinterpret_cast<Message*>(const_cast<char*>(actual->message)) - 1;
foreignObject = other;
foreignMessage = plain ? nullptr : reinterpret_cast<Message*>(const_cast<char*>(other->message)) - 1;
TrackFree();
auto* unused = std::strstr(mode, "zero") ? nullptr : other;
GuestDestroy destroy, deleteObject;
GuestWhat what;
auto* slots = static_cast<const unsigned char*>(actual->vtable);
std::memcpy(&destroy, slots, sizeof(destroy));
std::memcpy(&deleteObject, slots + sizeof(void*), sizeof(deleteObject));
std::memcpy(&what, slots + 2 * sizeof(void*), sizeof(what));
if (destroying) {
ProbeDestroy(deleting ? deleteObject : destroy, actual, unused);
if (objectFrees != (deleting ? 1u : 0u) || messageFrees != (plain ? 0u : 1u)) FixtureFail(58);
if (!deleting && actual->message) FixtureFail(59);
} else {
const char* result = ProbeWhat(what, actual, unused);
if (!result || std::strcmp(result, plain ? "std::bad_alloc" : "actual virtual message") ||
objectFrees || messageFrees) FixtureFail(60);
}
if (other->vtable != originalOther.vtable || other->message != originalOther.message ||
(!plain && std::strcmp(other->message, "unrelated virtual message"))) FixtureFail(61);
ReplaceFree(reinterpret_cast<std::uintptr_t>(originalFree));
if (!plain) {
if (!deleting) _ZNSt12out_of_rangeD1Ev_nid_postfix(actual);
_ZNSt12out_of_rangeD1Ev_nid_postfix(other);
}
if (!deleting) std::free(actual);
std::free(other);
return 0;
}
static Message* MessageHeader(const Object& object) {
return reinterpret_cast<Message*>(const_cast<char*>(object.message)) - 1;
}
extern "C" void APS5_VABI ObserveDestroy(void* pointer) {
if (pointer != expectedObject || ++callbacks != 1) FixtureFail(40);
ProbeDestroy(registeredDestructor, pointer, poison);
if (static_cast<Object*>(pointer)->message != nullptr ||
!decoy.message || std::strcmp(decoy.message, "unrelated message") ||
(keepMessage && MessageHeader(retained)->references.load() != 0)) FixtureFail(41);
}
extern "C" void APS5_VABI FixtureInspect(void* pointer) {
auto* header = LibcException::FromObject(pointer);
if (header->_pad != 0 || !header->destructor) FixtureFail(42);
expectedObject = pointer;
GuestWhat what;
std::memcpy(&what, static_cast<const unsigned char*>(static_cast<Object*>(pointer)->vtable) +
2 * sizeof(void*), sizeof(what));
const char* message = ProbeWhat(what, pointer, poison);
if (!message || std::strcmp(message, expectedMessage)) FixtureFail(62);
if (keepMessage) {
_ZNSt12out_of_rangeC1ERKS__nid_postfix(&retained, static_cast<Object*>(pointer));
if (MessageHeader(retained)->references.load() != 1) FixtureFail(43);
}
registeredDestructor = reinterpret_cast<GuestDestroy>(header->destructor);
if (!regularRelease) header->destructor = reinterpret_cast<HostDestroy>(ObserveDestroy);
}
int main(int argc, char** argv) {
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
if (argc != 2) return 2;
if (std::strncmp(argv[1], "vtable-", 7) == 0) return TestVtable(argv[1]);
if (std::strcmp(argv[1], "control") == 0) {
if (GuestOuter() != 0 || FixtureDestroyed != 1 || FixtureGuardDestroyed != 1 ||
FixtureCaught != 2 || FixtureWhatChecked != 2) return 10;
return 0;
}
const bool regex = std::strstr(argv[1], "regex") != nullptr;
regularRelease = std::strcmp(argv[1], "release") == 0;
keepMessage = std::strstr(argv[1], "unshared") == nullptr;
FixtureArgument = reinterpret_cast<std::uintptr_t>(FixtureMessage);
if (regex) {
expectedMessage = "regular expression error";
FixtureStdOutType = _ZTISt11regex_error_nid_postfix;
FixtureThrow = reinterpret_cast<GuestThrowFunction>(_ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix);
FixtureArgument = static_cast<std::uintptr_t>(std::regex_constants::error_collate);
}
_ZNSt12out_of_rangeC1EPKc_nid_postfix(&decoy, "unrelated message");
poison = std::strstr(argv[1], "zero") ? nullptr : &decoy;
GuestStdCapture();
if (callbacks != (regularRelease ? 0u : 1u) ||
(keepMessage && (MessageHeader(retained)->references.load() != 0 ||
std::strcmp(retained.message, regex ? "regular expression error" : FixtureMessage))) ||
!decoy.message || std::strcmp(decoy.message, "unrelated message")) return 44;
_ZNSt12out_of_rangeD1Ev_nid_postfix(&retained);
_ZNSt12out_of_rangeD1Ev_nid_postfix(&decoy);
if (retained.message || decoy.message) return 45;
std::puts("guest message callback, retained ownership and release: PASS");
return 0;
}