feat(libkernel): implement sceKernelGetModuleList2 and sceKernelGetModuleInfo2
The title-module variants of sceKernelGetModuleList and sceKernelGetModuleInfo, following shadPS4: the list leaves out system libraries and the info call answers EPERM for one. A system library is a loaded .prx image that is not a relinked title module. NHL 27 (PPSA34063) imports both and fails to load without them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d592d54e11
commit
08824f3b90
|
|
@ -205,6 +205,12 @@ std::uint64_t GuestSegmentBytes() {
|
|||
dl_iterate_phdr(AddGuestSegmentBytes, &bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bool SystemImage(const GuestImage& image) {
|
||||
const auto* name = static_cast<const link_map*>(image.native)->l_name;
|
||||
const std::string_view path = name ? name : "";
|
||||
return !path.empty() && !path.ends_with(GuestModuleSuffix);
|
||||
}
|
||||
#else
|
||||
bool SectionNamed(const IMAGE_SECTION_HEADER& section, const char* name) {
|
||||
return std::strncmp(reinterpret_cast<const char*>(section.Name), name, IMAGE_SIZEOF_SHORT_NAME) == 0;
|
||||
|
|
@ -341,8 +347,46 @@ std::uint64_t GuestSegmentBytes() {
|
|||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bool SystemImage(const GuestImage& image) {
|
||||
if (image.address == reinterpret_cast<std::uintptr_t>(GetModuleHandleW(nullptr))) return false;
|
||||
constexpr std::wstring_view suffix = L".guest.prx";
|
||||
const auto path = ModulePath(reinterpret_cast<HMODULE>(image.address));
|
||||
return path.size() < suffix.size() || CompareStringOrdinal(path.data() + path.size() - suffix.size(), static_cast<int>(suffix.size()), suffix.data(), static_cast<int>(suffix.size()), TRUE) != CSTR_EQUAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
int FillModuleInfo(KernelModule handle, ModuleInfo* info, bool titleOnly) {
|
||||
if (!info) return SCE_KERNEL_ERROR_EFAULT;
|
||||
if (info->st_size != sizeof(ModuleInfo)) return SCE_KERNEL_ERROR_EINVAL;
|
||||
for (const auto& image : GuestImages()) {
|
||||
if (ModuleIdForImage_nid_no_patch(image.native) != handle) continue;
|
||||
if (titleOnly && SystemImage(image)) return SCE_KERNEL_ERROR_EPERM;
|
||||
ModuleInfo result{};
|
||||
result.st_size = sizeof(ModuleInfo);
|
||||
#ifdef _WIN32
|
||||
if (!Fill(image.address, result)) return SCE_KERNEL_ERROR_ESRCH;
|
||||
#else
|
||||
ImageSearch<ModuleInfo> search{image.address, &result, false};
|
||||
dl_iterate_phdr(FindImage<ModuleInfo>, &search);
|
||||
if (!search.found) return SCE_KERNEL_ERROR_ESRCH;
|
||||
#endif
|
||||
*info = result;
|
||||
return 0;
|
||||
}
|
||||
return SCE_KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
|
||||
int FillModuleList(KernelModule* handles, std::size_t count, std::size_t* actual, bool titleOnly) {
|
||||
if (!handles || !actual) return SCE_KERNEL_ERROR_EFAULT;
|
||||
auto images = GuestImages();
|
||||
if (titleOnly) std::erase_if(images, SystemImage);
|
||||
for (std::size_t i = 0; i < images.size() && i < count; ++i) handles[i] = ModuleIdForImage_nid_no_patch(images[i].native);
|
||||
if (images.size() > count) return SCE_KERNEL_ERROR_ENOMEM;
|
||||
*actual = images.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
@ -369,32 +413,19 @@ int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, M
|
|||
}
|
||||
|
||||
int APS5_VABI sceKernelGetModuleList(KernelModule* handles, std::size_t count, std::size_t* actual) {
|
||||
if (!handles || !actual) return SCE_KERNEL_ERROR_EFAULT;
|
||||
const auto images = GuestImages();
|
||||
for (std::size_t i = 0; i < images.size() && i < count; ++i) handles[i] = ModuleIdForImage_nid_no_patch(images[i].native);
|
||||
if (images.size() > count) return SCE_KERNEL_ERROR_ENOMEM;
|
||||
*actual = images.size();
|
||||
return 0;
|
||||
return FillModuleList(handles, count, actual, false);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelGetModuleList2(KernelModule* handles, std::size_t count, std::size_t* actual) {
|
||||
return FillModuleList(handles, count, actual, true);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelGetModuleInfo(KernelModule handle, ModuleInfo* info) {
|
||||
if (!info) return SCE_KERNEL_ERROR_EFAULT;
|
||||
if (info->st_size != sizeof(ModuleInfo)) return SCE_KERNEL_ERROR_EINVAL;
|
||||
for (const auto& image : GuestImages()) {
|
||||
if (ModuleIdForImage_nid_no_patch(image.native) != handle) continue;
|
||||
ModuleInfo result{};
|
||||
result.st_size = sizeof(ModuleInfo);
|
||||
#ifdef _WIN32
|
||||
if (!Fill(image.address, result)) return SCE_KERNEL_ERROR_ESRCH;
|
||||
#else
|
||||
ImageSearch<ModuleInfo> search{image.address, &result, false};
|
||||
dl_iterate_phdr(FindImage<ModuleInfo>, &search);
|
||||
if (!search.found) return SCE_KERNEL_ERROR_ESRCH;
|
||||
#endif
|
||||
*info = result;
|
||||
return 0;
|
||||
}
|
||||
return SCE_KERNEL_ERROR_ESRCH;
|
||||
return FillModuleInfo(handle, info, false);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelGetModuleInfo2(KernelModule handle, ModuleInfo* info) {
|
||||
return FillModuleInfo(handle, info, true);
|
||||
}
|
||||
|
||||
std::uint64_t GuestImageSegmentBytes_nid_no_patch() {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ int APS5_VABI dlclose_nid_postfix(void*);
|
|||
int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t, int, ModuleInfoEx*);
|
||||
int APS5_VABI sceKernelGetModuleList(KernelModule*, std::size_t, std::size_t*);
|
||||
int APS5_VABI sceKernelGetModuleInfo(KernelModule, ModuleInfo*);
|
||||
int APS5_VABI sceKernelGetModuleList2(KernelModule*, std::size_t, std::size_t*);
|
||||
int APS5_VABI sceKernelGetModuleInfo2(KernelModule, ModuleInfo*);
|
||||
}
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
template<typename TFunction>
|
||||
|
|
@ -105,6 +107,36 @@ int main(int argc, char** argv) {
|
|||
unknown.st_size = sizeof(ModuleInfo);
|
||||
Require(sceKernelGetModuleInfo(info.id, &unknown) == SCE_KERNEL_ERROR_ESRCH);
|
||||
Require(sceKernelGetModuleInfo(-1, &unknown) == SCE_KERNEL_ERROR_ESRCH && unknown.name[0] == '\0');
|
||||
std::vector<KernelModule> titleHandles(handles.size() + 1, -1);
|
||||
std::size_t titleCount = 0;
|
||||
Require(sceKernelGetModuleList2(titleHandles.data(), titleHandles.size(), &titleCount) == 0);
|
||||
Require(titleCount >= 1 && titleCount < count && titleHandles[titleCount] == -1);
|
||||
titleHandles.resize(titleCount);
|
||||
Require(titleHandles.front() == self.id);
|
||||
for (const auto handle : titleHandles) {
|
||||
Require(std::count(handles.begin(), handles.end(), handle) == 1);
|
||||
ModuleInfo title{}, listed{};
|
||||
title.st_size = listed.st_size = sizeof(ModuleInfo);
|
||||
Require(sceKernelGetModuleInfo2(handle, &title) == 0 && sceKernelGetModuleInfo(handle, &listed) == 0);
|
||||
Require(std::string(title.name) == listed.name && title.segment_count == listed.segment_count);
|
||||
}
|
||||
std::size_t titleUntouched = 99;
|
||||
Require(sceKernelGetModuleList2(titleHandles.data(), titleCount - 1, &titleUntouched) == SCE_KERNEL_ERROR_ENOMEM && titleUntouched == 99);
|
||||
Require(sceKernelGetModuleList2(nullptr, 0, &titleUntouched) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Require(sceKernelGetModuleList2(titleHandles.data(), titleHandles.size(), nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
for (const auto handle : handles) {
|
||||
if (std::count(titleHandles.begin(), titleHandles.end(), handle) != 0) continue;
|
||||
ModuleInfo system{};
|
||||
system.st_size = sizeof(ModuleInfo);
|
||||
system.name[0] = 'x';
|
||||
Require(sceKernelGetModuleInfo2(handle, &system) == SCE_KERNEL_ERROR_EPERM && system.name[0] == 'x');
|
||||
Require(sceKernelGetModuleInfo2(handle, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
system.st_size = 0x158;
|
||||
Require(sceKernelGetModuleInfo2(handle, &system) == SCE_KERNEL_ERROR_EINVAL);
|
||||
}
|
||||
ModuleInfo absent{};
|
||||
absent.st_size = sizeof(ModuleInfo);
|
||||
Require(sceKernelGetModuleInfo2(-1, &absent) == SCE_KERNEL_ERROR_ESRCH && absent.name[0] == 0);
|
||||
int local = 0;
|
||||
Query(&local, SCE_KERNEL_ERROR_ESRCH);
|
||||
Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast<std::uintptr_t>(add), 2, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceKernelGetModuleInfoFromAddr](../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the 0x1A8-byte info layout follows the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation; a title's `libc.prx` reads only `id` (PPSA14632). The meaning of `flags` is unknown; only 2 is accepted. The input `st_size` is ignored, because PPSA21703's `libc.prx` passes an uninitialized 0x1A8-byte buffer; the full size is written back. The id is the `sceKernelLoadStartModule` handle of an image opened through the loader and otherwise a stable id from the same range. The name is the host file name without `.guest.prx`. An image with more than 4 load segments (the converted executable has 7) reports its first 4. `tls_offset` is 0 and `ref_count` is 1, as the host loader exposes neither. On Windows the module is the PE image holding the address: a relinked image reports its `.elf` sections as segments and the eh_frame_hdr recorded in `.ehmeta`, any other image its first 4 sections with memory access and the `.ehfram` section without an eh_frame_hdr; the TLS fields and `init_proc_addr`/`fini_proc_addr` are 0
|
||||
- [sceKernelGetModuleList](../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - lists the main program and every loaded `.prx` image in load order; host runtime libraries are left out. The handles are the ids `sceKernelGetModuleInfoFromAddr` reports, not the console's. The `(int32_t* handles, size_t count, size_t* actual)` signature and returning `ENOMEM` without writing `actual` after filling the first `count` handles when more images are loaded follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/kernel/process.cpp#L244-L264); whether the console writes any handles in that case is unknown
|
||||
- [sceKernelGetModuleInfo](../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the 0x160-byte layout (`st_size`, `name[256]`, 4 segments, `segment_count`, `fingerprint[20]`) follows [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/sys/sys_types.pas#L319-L337) and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/module.h#L15-L31). Only that `st_size` is accepted, as in shadPS4; other sizes return `EINVAL`, a null pointer `EFAULT` and a handle `sceKernelGetModuleList` does not list `ESRCH`. Name and segments are the ones `sceKernelGetModuleInfoFromAddr` reports, on Windows from the same PE sections, so `prot` uses the read/write/execute bits 1/2/4, while fpPS4 and shadPS4 store the ELF `p_flags`. The fingerprint is left zero, as the host image does not carry it
|
||||
- [sceKernelGetModuleList2 and sceKernelGetModuleInfo2](../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the title-module variants follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/process.cpp): `sceKernelGetModuleList2` lists what `sceKernelGetModuleList` lists without system libraries, and `sceKernelGetModuleInfo2` answers `EPERM` for a system library after the `EFAULT`, `EINVAL` and `ESRCH` checks of `sceKernelGetModuleInfo`. A system library here is a loaded `.prx` image that is not a relinked title module (`.guest.prx`); the main program and the title's own modules are listed. Not verified on the console. PPSA34063 imports both and fails to load without them
|
||||
- [sceKernelReleaseFlexibleMemory](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - unmaps the range like sceKernelMunmap, as fpPS4 and prosper do; whether the console keeps the virtual range reserved after releasing its pages is unverified
|
||||
- [sceKernelAvailableFlexibleMemorySize](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - the segments of the title's executable and of its `.guest.prx` modules count against the 448 MiB flexible budget while they are loaded, for this size and for the flexible maps, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/module.cpp#L165) maps every segment of a module outside its system module directory as flexible memory on the PS4. [Counted](../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) are the segments the title's ELF gives permissions, each rounded to 16 KiB pages; not counted are segments without permissions, the relinker's own blocks (on Linux both are the read/write/execute `PT_LOAD`s, plus the header block at file offset 0; on Windows everything outside the `.elf` sections) and the host system libraries. The Linux image carries no marker for the relinker's blocks, so they are told apart by those flags and that offset only: a title segment that is writable and executable, or starts at file offset 0, is not counted. A module whose segments exceed what is left still loads, and the available size then reads 0; shadPS4 fails the segment map instead ([memory.cpp](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/memory.cpp#L519-L524) returns `EINVAL`, [module.cpp](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/module.cpp#L168-L169) asserts). Which segments the PS5 charges, its rounding, whether system modules use the title's budget and when an unload releases the charge are not measured. PPSA15508 subtracts the reported size and a 10 MiB module allowance from its own 448 MiB budget at start-up and stops when the result exceeds its 146 MiB code and data limit ([#2883](https://github.com/boykopovar/AnyPS5/issues/2883))
|
||||
- [Memory protection bits 0x100 and 0x200](../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - taken as audio coprocessor (ACP) read and write, as in the [KytyPS5](https://github.com/KytyPS5/KytyPS5) and [etaHEN](https://github.com/etaHEN/etaHEN) headers; the coprocessor work runs on the host CPU, so they grant host read and read/write access like the GPU bits
|
||||
|
|
|
|||
Loading…
Reference in New Issue