#include "align.hpp" #include "amdgpu/bridge/bridge.hpp" #include "backtrace.hpp" #include "bridge.hpp" #include "io-device.hpp" #include "io-devices.hpp" #include "linker.hpp" #include "ops.hpp" #include "thread.hpp" #include "vfs.hpp" #include "vm.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int g_gpuPid; __attribute__((no_stack_protector)) static void handle_signal(int sig, siginfo_t *info, void *ucontext) { if (auto hostFs = _readgsbase_u64()) { _writefsbase_u64(hostFs); } auto signalAddress = reinterpret_cast(info->si_addr); if (orbis::g_currentThread != nullptr && sig == SIGSEGV && signalAddress >= 0x40000 && signalAddress < 0x100'0000'0000) { auto ctx = reinterpret_cast(ucontext); bool isWrite = (ctx->uc_mcontext.gregs[REG_ERR] & 0x2) != 0; auto origVmProt = rx::vm::getPageProtection(signalAddress); int prot = 0; auto page = signalAddress / amdgpu::bridge::kHostPageSize; if (origVmProt & rx::vm::kMapProtCpuRead) { prot |= PROT_READ; } if (origVmProt & rx::vm::kMapProtCpuWrite) { prot |= PROT_WRITE; } if (origVmProt & rx::vm::kMapProtCpuExec) { prot |= PROT_EXEC; } if (prot & (isWrite ? PROT_WRITE : PROT_READ)) { auto bridge = rx::bridge.header; while (true) { auto flags = bridge->cachePages[page].load(std::memory_order::relaxed); if ((flags & amdgpu::bridge::kPageReadWriteLock) != 0) { if ((flags & amdgpu::bridge::kPageLazyLock) != 0) { if (std::uint32_t gpuCommand = 0; !bridge->gpuCacheCommand.compare_exchange_weak(gpuCommand, page)) { continue; } while (!bridge->cachePages[page].compare_exchange_weak( flags, flags & ~amdgpu::bridge::kPageLazyLock, std::memory_order::relaxed)) { } } continue; } if ((flags & amdgpu::bridge::kPageWriteWatch) == 0) { break; } if (!isWrite) { prot &= ~PROT_WRITE; break; } if (bridge->cachePages[page].compare_exchange_weak( flags, amdgpu::bridge::kPageInvalidated, std::memory_order::relaxed)) { break; } } if (::mprotect((void *)(page * amdgpu::bridge::kHostPageSize), amdgpu::bridge::kHostPageSize, prot)) { std::perror("cache reprotection error"); std::abort(); } _writefsbase_u64(orbis::g_currentThread->fsBase); return; } std::fprintf(stderr, "SIGSEGV, address %lx, access %s, prot %s\n", signalAddress, isWrite ? "write" : "read", rx::vm::mapProtToString(origVmProt).c_str()); } if (g_gpuPid > 0) { // stop gpu thread ::kill(g_gpuPid, SIGINT); } if (sig != SIGINT) { char buf[128] = ""; int len = snprintf(buf, sizeof(buf), " [%s] %u: Signal address=%p\n", orbis::g_currentThread ? "guest" : "host", orbis::g_currentThread ? orbis::g_currentThread->tid : ::gettid(), info->si_addr); write(2, buf, len); if (std::size_t printed = rx::printAddressLocation(buf, sizeof(buf), orbis::g_currentThread, (std::uint64_t)info->si_addr)) { printed += std::snprintf(buf + printed, sizeof(buf) - printed, "\n"); write(2, buf, printed); } if (orbis::g_currentThread) { rx::printStackTrace(reinterpret_cast(ucontext), orbis::g_currentThread, 2); } else { rx::printStackTrace(reinterpret_cast(ucontext), 2); } } struct sigaction act {}; sigset_t mask; sigemptyset(&mask); act.sa_handler = SIG_DFL; act.sa_flags = SA_SIGINFO | SA_ONSTACK; act.sa_mask = mask; if (sigaction(sig, &act, NULL)) { perror("Error sigaction:"); std::exit(-1); } if (sig == SIGINT) { std::raise(SIGINT); } } void setupSigHandlers() { stack_t oss{}; // if (sigaltstack(nullptr, &oss) < 0 || oss.ss_size == 0) { auto sigStackSize = std::max( SIGSTKSZ, utils::alignUp(64 * 1024 * 1024, sysconf(_SC_PAGE_SIZE))); stack_t ss{}; ss.ss_sp = malloc(sigStackSize); if (ss.ss_sp == NULL) { perror("malloc"); exit(EXIT_FAILURE); } ss.ss_size = sigStackSize; ss.ss_flags = 1 << 31; std::fprintf(stderr, "installing sp [%p, %p]\n", ss.ss_sp, (char *)ss.ss_sp + ss.ss_size); if (sigaltstack(&ss, NULL) == -1) { perror("sigaltstack"); exit(EXIT_FAILURE); } // } struct sigaction act {}; act.sa_sigaction = handle_signal; act.sa_flags = SA_SIGINFO | SA_ONSTACK; if (sigaction(SIGSYS, &act, NULL)) { perror("Error sigaction:"); exit(-1); } if (sigaction(SIGILL, &act, NULL)) { perror("Error sigaction:"); exit(-1); } if (sigaction(SIGSEGV, &act, NULL)) { perror("Error sigaction:"); exit(-1); } if (sigaction(SIGBUS, &act, NULL)) { perror("Error sigaction:"); exit(-1); } if (sigaction(SIGABRT, &act, NULL)) { perror("Error sigaction:"); exit(-1); } if (sigaction(SIGINT, &act, NULL)) { perror("Error sigaction:"); exit(-1); } } struct StackWriter { std::uint64_t address; template std::uint64_t push(T value) { address -= sizeof(value); address &= ~(alignof(T) - 1); *reinterpret_cast(address) = value; return address; } void align(std::uint64_t alignment) { address &= ~(alignment - 1); } std::uint64_t pushString(const char *value) { auto len = std::strlen(value); address -= len + 1; std::memcpy(reinterpret_cast(address), value, len + 1); return address; } std::uint64_t alloc(std::uint64_t size, std::uint64_t alignment) { address -= size; address &= ~(alignment - 1); return address; } }; static bool g_traceSyscalls = false; static const char *getSyscallName(orbis::Thread *thread, int sysno) { auto sysvec = thread->tproc->sysent; if (sysno >= sysvec->size) { return nullptr; } return orbis::getSysentName(sysvec->table[sysno].call); } static void onSysEnter(orbis::Thread *thread, int id, uint64_t *args, int argsCount) { if (!g_traceSyscalls) { return; } flockfile(stderr); std::fprintf(stderr, " [%u] ", thread->tid); if (auto name = getSyscallName(thread, id)) { std::fprintf(stderr, "%s(", name); } else { std::fprintf(stderr, "sys_%u(", id); } for (int i = 0; i < argsCount; ++i) { if (i != 0) { std::fprintf(stderr, ", "); } std::fprintf(stderr, "%#lx", args[i]); } std::fprintf(stderr, ")\n"); funlockfile(stderr); } static void onSysExit(orbis::Thread *thread, int id, uint64_t *args, int argsCount, orbis::SysResult result) { if (!result.isError() && !g_traceSyscalls) { return; } flockfile(stderr); std::fprintf(stderr, "%c: [%u] ", result.isError() ? 'E' : 'S', thread->tid); if (auto name = getSyscallName(thread, id)) { std::fprintf(stderr, "%s(", name); } else { std::fprintf(stderr, "sys_%u(", id); } for (int i = 0; i < argsCount; ++i) { if (i != 0) { std::fprintf(stderr, ", "); } std::fprintf(stderr, "%#lx", args[i]); } std::fprintf(stderr, ") -> Status %d, Value %lx:%lx\n", result.value(), thread->retval[0], thread->retval[1]); thread->where(); funlockfile(stderr); } static void ps4InitDev() { auto dmem1 = createDmemCharacterDevice(1); orbis::g_context.dmemDevice = dmem1; auto consoleDev = createConsoleCharacterDevice( STDIN_FILENO, ::open("tty.txt", O_CREAT | O_TRUNC | O_WRONLY, 0666)); rx::vfs::addDevice("dmem0", createDmemCharacterDevice(0)); rx::vfs::addDevice("npdrm", createNpdrmCharacterDevice()); rx::vfs::addDevice("icc_configuration", createIccConfigurationCharacterDevice()); rx::vfs::addDevice("console", consoleDev); rx::vfs::addDevice("camera", createCameraCharacterDevice()); rx::vfs::addDevice("dmem1", dmem1); rx::vfs::addDevice("dmem2", createDmemCharacterDevice(2)); rx::vfs::addDevice("stdout", consoleDev); rx::vfs::addDevice("stderr", consoleDev); rx::vfs::addDevice("deci_stdin", consoleDev); rx::vfs::addDevice("deci_stdout", consoleDev); rx::vfs::addDevice("deci_stderr", consoleDev); rx::vfs::addDevice("stdin", consoleDev); rx::vfs::addDevice("zero", createZeroCharacterDevice()); rx::vfs::addDevice("null", createNullCharacterDevice()); rx::vfs::addDevice("dipsw", createDipswCharacterDevice()); rx::vfs::addDevice("dce", createDceCharacterDevice()); rx::vfs::addDevice("hmd_cmd", createHmdCmdCharacterDevice()); rx::vfs::addDevice("hmd_snsr", createHmdSnsrCharacterDevice()); rx::vfs::addDevice("hmd_3da", createHmd3daCharacterDevice()); rx::vfs::addDevice("hmd_dist", createHmdMmapCharacterDevice()); rx::vfs::addDevice("hid", createHidCharacterDevice()); rx::vfs::addDevice("gc", createGcCharacterDevice()); rx::vfs::addDevice("rng", createRngCharacterDevice()); rx::vfs::addDevice("sbl_srv", createSblSrvCharacterDevice()); rx::vfs::addDevice("ajm", createAjmCharacterDevice()); rx::vfs::addDevice("urandom", createUrandomCharacterDevice()); rx::vfs::addDevice("mbus", createMBusCharacterDevice()); rx::vfs::addDevice("metadbg", createMetaDbgCharacterDevice()); rx::vfs::addDevice("bt", createBtCharacterDevice()); rx::vfs::addDevice("xpt0", createXptCharacterDevice()); rx::vfs::addDevice("cd0", createXptCharacterDevice()); rx::vfs::addDevice("da0x0.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x1.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x2.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x3.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x4.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x5.crypt", createHddCharacterDevice()); // rx::vfs::addDevice("da0x6x0", createHddCharacterDevice()); // boot log rx::vfs::addDevice("da0x6x2.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x8", createHddCharacterDevice()); rx::vfs::addDevice("da0x9.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x12.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x13.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x14.crypt", createHddCharacterDevice()); rx::vfs::addDevice("da0x15", createHddCharacterDevice()); rx::vfs::addDevice("notification0", createNotificationCharacterDevice(0)); rx::vfs::addDevice("notification1", createNotificationCharacterDevice(1)); rx::vfs::addDevice("notification2", createNotificationCharacterDevice(2)); rx::vfs::addDevice("notification3", createNotificationCharacterDevice(3)); rx::vfs::addDevice("notification4", createNotificationCharacterDevice(4)); rx::vfs::addDevice("notification5", createNotificationCharacterDevice(5)); rx::vfs::addDevice("aout0", createAoutCharacterDevice()); rx::vfs::addDevice("av_control", createAVControlCharacterDevice()); rx::vfs::addDevice("hdmi", createHDMICharacterDevice()); rx::vfs::addDevice("mbus_av", createMBusAVCharacterDevice()); rx::vfs::addDevice("scanin", createScaninCharacterDevice()); orbis::g_context.shmDevice = createShmDevice(); orbis::g_context.blockpoolDevice = createBlockPoolDevice(); } static void ps4InitFd(orbis::Thread *mainThread) { orbis::Ref stdinFile; orbis::Ref stdoutFile; orbis::Ref stderrFile; rx::procOpsTable.open(mainThread, "/dev/stdin", 0, 0, &stdinFile); rx::procOpsTable.open(mainThread, "/dev/stdout", 0, 0, &stdoutFile); rx::procOpsTable.open(mainThread, "/dev/stderr", 0, 0, &stderrFile); mainThread->tproc->fileDescriptors.insert(stdinFile); mainThread->tproc->fileDescriptors.insert(stdoutFile); mainThread->tproc->fileDescriptors.insert(stderrFile); } int ps4Exec(orbis::Thread *mainThread, orbis::utils::Ref executableModule, std::span argv, std::span envp) { const auto stackEndAddress = 0x7'ffff'c000ull; const auto stackSize = 0x40000 * 32; auto stackStartAddress = stackEndAddress - stackSize; mainThread->stackStart = rx::vm::map(reinterpret_cast(stackStartAddress), stackSize, rx::vm::kMapProtCpuWrite | rx::vm::kMapProtCpuRead, rx::vm::kMapFlagAnonymous | rx::vm::kMapFlagFixed | rx::vm::kMapFlagPrivate | rx::vm::kMapFlagStack); mainThread->stackEnd = reinterpret_cast(mainThread->stackStart) + stackSize; std::vector argvOffsets; std::vector envpOffsets; std::uint64_t interpBase = 0; std::uint64_t entryPoint = executableModule->entryPoint; if (orbis::g_context.sdkVersion == 0 && mainThread->tproc->processParam) { auto processParam = reinterpret_cast(mainThread->tproc->processParam); auto sdkVersion = processParam // + sizeof(uint64_t) // size + sizeof(uint32_t) // magic + sizeof(uint32_t); // entryCount orbis::g_context.sdkVersion = *(uint32_t *)sdkVersion; } if (executableModule->type != rx::linker::kElfTypeExec) { auto libSceLibcInternal = rx::linker::loadModuleFile( "/system/common/lib/libSceLibcInternal.sprx", mainThread); if (libSceLibcInternal == nullptr) { std::fprintf(stderr, "libSceLibcInternal not found\n"); return 1; } libSceLibcInternal->id = mainThread->tproc->modulesMap.insert(libSceLibcInternal); auto libkernel = rx::linker::loadModuleFile( "/system/common/lib/libkernel_sys.sprx", mainThread); if (libkernel == nullptr) { std::fprintf(stderr, "libkernel not found\n"); return 1; } libkernel->id = mainThread->tproc->modulesMap.insert(libkernel); interpBase = reinterpret_cast(libkernel->base); entryPoint = libkernel->entryPoint; // *reinterpret_cast( // reinterpret_cast(libkernel->base) + 0x6c2e4) = ~0; // *reinterpret_cast( // reinterpret_cast(libkernel->base) + 0x71300) = ~0; } StackWriter stack{reinterpret_cast(mainThread->stackEnd)}; for (auto &elem : argv) { argvOffsets.push_back(stack.pushString(elem.data())); } argvOffsets.push_back(0); for (auto &elem : envp) { envpOffsets.push_back(stack.pushString(elem.data())); } envpOffsets.push_back(0); // clang-format off std::uint64_t auxv[] = { AT_ENTRY, executableModule->entryPoint, AT_BASE, interpBase, AT_NULL, 0 }; // clang-format on std::size_t argSize = sizeof(std::uint64_t) + sizeof(std::uint64_t) * argvOffsets.size() + sizeof(std::uint64_t) * envpOffsets.size() + sizeof(auxv); auto sp = stack.alloc(argSize, 32); auto arg = reinterpret_cast(sp); *arg++ = argvOffsets.size() - 1; for (auto argvOffsets : argvOffsets) { *arg++ = argvOffsets; } for (auto envpOffset : envpOffsets) { *arg++ = envpOffset; } executableModule = {}; memcpy(arg, auxv, sizeof(auxv)); auto context = new ucontext_t{}; context->uc_mcontext.gregs[REG_RDI] = sp; context->uc_mcontext.gregs[REG_RSP] = sp; // FIXME: should be at guest user space context->uc_mcontext.gregs[REG_RDX] = reinterpret_cast(+[] { std::printf("At exit\n"); }); context->uc_mcontext.gregs[REG_RIP] = entryPoint; mainThread->context = context; rx::thread::invoke(mainThread); std::abort(); } static void usage(const char *argv0) { std::printf("%s [...] [args...]\n", argv0); std::printf(" options:\n"); std::printf(" -m, --mount \n"); std::printf(" -a, --enable-audio\n"); std::printf(" -o, --override \n"); std::printf(" --trace\n"); } static std::filesystem::path getSelfDir() { char path[PATH_MAX]; int len = ::readlink("/proc/self/exe", path, sizeof(path)); if (len < 0 || len >= sizeof(path)) { // TODO return std::filesystem::current_path(); } return std::filesystem::path(path).parent_path(); } static bool isRpsxGpuPid(int pid) { if (pid <= 0 || ::kill(pid, 0) != 0) { return false; } char path[PATH_MAX]; std::string procPath = "/proc/" + std::to_string(pid) + "/exe"; auto len = ::readlink(procPath.c_str(), path, sizeof(path)); if (len < 0 || len >= std::size(path)) { return false; } path[len] = 0; std::printf("filename is '%s'\n", std::filesystem::path(path).filename().c_str()); return std::filesystem::path(path).filename() == "rpcsx-gpu"; } static void runRpsxGpu() { const char *cmdBufferName = "/rpcsx-gpu-cmds"; amdgpu::bridge::BridgeHeader *bridgeHeader = amdgpu::bridge::openShmCommandBuffer(cmdBufferName); if (bridgeHeader != nullptr && bridgeHeader->pullerPid > 0 && isRpsxGpuPid(bridgeHeader->pullerPid)) { bridgeHeader->pusherPid = ::getpid(); g_gpuPid = bridgeHeader->pullerPid; rx::bridge = bridgeHeader; return; } std::printf("Starting rpcsx-gpu\n"); if (bridgeHeader == nullptr) { bridgeHeader = amdgpu::bridge::createShmCommandBuffer(cmdBufferName); } bridgeHeader->pusherPid = ::getpid(); rx::bridge = bridgeHeader; auto rpcsxGpuPath = getSelfDir() / "rpcsx-gpu"; if (!std::filesystem::is_regular_file(rpcsxGpuPath)) { std::printf("failed to find rpcsx-gpu, continue without GPU emulation\n"); return; } g_gpuPid = ::fork(); if (g_gpuPid == 0) { // TODO const char *argv[] = {rpcsxGpuPath.c_str(), nullptr}; ::execv(rpcsxGpuPath.c_str(), const_cast(argv)); } } int main(int argc, const char *argv[]) { if (argc == 2) { if (std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) { usage(argv[0]); return 1; } } if (argc < 2) { usage(argv[0]); return 1; } setupSigHandlers(); rx::vfs::initialize(); bool enableAudio = false; bool asRoot = false; bool isSystem = false; int argIndex = 1; while (argIndex < argc) { if (argv[argIndex] == std::string_view("--mount") || argv[argIndex] == std::string_view("-m")) { if (argc <= argIndex + 2) { usage(argv[0]); return 1; } std::printf("mounting '%s' to virtual '%s'\n", argv[argIndex + 1], argv[argIndex + 2]); if (!std::filesystem::is_directory(argv[argIndex + 1])) { std::fprintf(stderr, "Directory '%s' not exists\n", argv[argIndex + 1]); return 1; } rx::vfs::mount(argv[argIndex + 2], createHostIoDevice(argv[argIndex + 1])); argIndex += 3; continue; } if (argv[argIndex] == std::string_view("--trace")) { argIndex++; g_traceSyscalls = true; continue; } if (argv[argIndex] == std::string_view("--root")) { argIndex++; asRoot = true; continue; } if (argv[argIndex] == std::string_view("--system")) { argIndex++; isSystem = true; continue; } if (argv[argIndex] == std::string_view("--override") || argv[argIndex] == std::string_view("-o")) { if (argc <= argIndex + 2) { usage(argv[0]); return 1; } rx::linker::override(argv[argIndex + 1], argv[argIndex + 2]); argIndex += 3; continue; } if (argv[argIndex] == std::string_view("--enable-audio") || argv[argIndex] == std::string_view("-a")) { argIndex++; enableAudio = true; continue; } break; } if (argIndex >= argc) { usage(argv[0]); return 1; } rx::thread::initialize(); rx::vm::initialize(); runRpsxGpu(); if (enableAudio) { orbis::g_context.audioOut = orbis::knew(); } // rx::vm::printHostStats(); orbis::g_context.allocatePid(); auto initProcess = orbis::g_context.createProcess(asRoot ? 1 : 10); pthread_setname_np(pthread_self(), "10.MAINTHREAD"); std::thread{[] { pthread_setname_np(pthread_self(), "Bridge"); auto bridge = rx::bridge.header; std::vector fetchedCommands; fetchedCommands.reserve(std::size(bridge->cacheCommands)); while (true) { for (auto &command : bridge->cacheCommands) { std::uint64_t value = command.load(std::memory_order::relaxed); if (value != 0) { fetchedCommands.push_back(value); command.store(0, std::memory_order::relaxed); } } if (fetchedCommands.empty()) { continue; } for (auto command : fetchedCommands) { auto page = static_cast(command); auto count = static_cast(command >> 32) + 1; auto pageFlags = bridge->cachePages[page].load(std::memory_order::relaxed); auto address = static_cast(page) * amdgpu::bridge::kHostPageSize; auto origVmProt = rx::vm::getPageProtection(address); int prot = 0; if (origVmProt & rx::vm::kMapProtCpuRead) { prot |= PROT_READ; } if (origVmProt & rx::vm::kMapProtCpuWrite) { prot |= PROT_WRITE; } if (origVmProt & rx::vm::kMapProtCpuExec) { prot |= PROT_EXEC; } if (pageFlags & amdgpu::bridge::kPageReadWriteLock) { prot &= ~(PROT_READ | PROT_WRITE); } else if (pageFlags & amdgpu::bridge::kPageWriteWatch) { prot &= ~PROT_WRITE; } // std::fprintf(stderr, "protection %lx-%lx\n", address, // address + amdgpu::bridge::kHostPageSize * count); if (::mprotect(reinterpret_cast(address), amdgpu::bridge::kHostPageSize * count, prot)) { perror("protection failed"); std::abort(); } } fetchedCommands.clear(); } }}.detach(); int status = 0; initProcess->sysent = &orbis::ps4_sysvec; initProcess->onSysEnter = onSysEnter; initProcess->onSysExit = onSysExit; initProcess->ops = &rx::procOpsTable; initProcess->appInfo = { .unk4 = (isSystem ? orbis::slong(0x80000000'00000000) : 0), }; auto [baseId, mainThread] = initProcess->threadsMap.emplace(); mainThread->tproc = initProcess; mainThread->tid = initProcess->pid + baseId; mainThread->state = orbis::ThreadState::RUNNING; auto executableModule = rx::linker::loadModuleFile(argv[argIndex], mainThread); if (executableModule == nullptr) { std::fprintf(stderr, "Failed to open '%s'\n", argv[argIndex]); std::abort(); } executableModule->id = initProcess->modulesMap.insert(executableModule); initProcess->processParam = executableModule->processParam; initProcess->processParamSize = executableModule->processParamSize; if (prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, (void *)0x100'0000'0000, ~0ull - 0x100'0000'0000, nullptr)) { perror("prctl failed\n"); exit(-1); } if (executableModule->type == rx::linker::kElfTypeSceDynExec || executableModule->type == rx::linker::kElfTypeSceExec || executableModule->type == rx::linker::kElfTypeExec) { ps4InitDev(); ps4InitFd(mainThread); std::vector ps4Argv(argv + argIndex, argv + argIndex + argc - argIndex); status = ps4Exec(mainThread, std::move(executableModule), ps4Argv, {}); } else { std::fprintf(stderr, "Unexpected executable type\n"); status = 1; } // rx::vm::printHostStats(); rx::vm::deinitialize(); rx::thread::deinitialize(); return status; }