PRX: Difference between revisions
CelesteBlue (talk | contribs) No edit summary |
|||
Line 1,156: | Line 1,156: | ||
| 255 || R_ARM_RBASE || Ignored | | 255 || R_ARM_RBASE || Ignored | ||
|} | |} | ||
== Tools == | |||
=== Tools for IDA PRO === | |||
* [https://github.com/xyzz/vitadump PS Vita PRX Loader for IDA PRO by xyz] | |||
* [https://github.com/xyzz/vita-ida-physdump PS Vita Physical Memory Dump Loader for IDA PRO by xyz] | |||
=== Tools for Ghidra === | |||
* [https://github.com/xerpi/GhidraVitaLoader PS Vita PRX Loader for Ghidra by xerpi] | |||
* [https://github.com/CreepNT/VitaLoaderRedux PS Vita PRX Loader for Ghidra by CreepNT] | |||
* PS Vita/PSP PRX Loader for Ghidra by CelesteBlue (link to add) | |||
=== Tools for Binary Ninja === | |||
* [https://github.com/computerman00/BinaryNinja-PSVitaLoader PS Vita PRX Loader for Binary Ninja by computerman00] |
Latest revision as of 08:26, 2 October 2024
SceModuleInfo
It contains information on the module, and on its imports/exports.
Location
- For non-stripped modules, SceModuleInfo structure is located in ".sceModuleInfo.rodata" memory block.
- For ET_SCE_EXEC modules, when e_entry is not null, SceModuleInfo structure is located in text segment at offset e_entry. Else it is located in text segment (first LOAD segment) at offset Elf32_Phdr[text_seg_id].p_paddr - Elf32_Phdr[text_seg_id].p_offset.
- For ET_SCE_RELEXEC modules, SceModuleInfo structure is located in the segment indexed by the upper two bits of e_entry of the ELF header. The structure is stored at the base offset of the segment plus the offset defined by the bottom 30 bits of e_entry.
Structure
Some fields are optional and can be set to zero. The other fields determine how this module is loaded and linked. All offset fields are formatted as follows: top 2 bits is an index to the segment to start at and bottom 30 bits is an offset from the segment start. Currently, the segment start index must match the segment that the module information structure is in.
Name | Description |
---|---|
modattribute | Attributes of the module. See #Module attribute |
modversion | Major version of the module (usually set to 1) followed by Minor version of the module (usually set to 1). |
modname | Name of the module. Not necessarily null-terminated string. |
terminal | Null-terminating character (0x00). Name was "terminal" since PSP. |
infoversion | SceModuleInfo version. Seen values are: 0 on PSP and PS3, 1 never seen, 2 on PS Vita FW 0.902, 3 on PS Vita FW 0.931.010, 4 and 5 never seen, 6 on PS Vita FWs 0.940-3.740.011. |
gp_value / resreve | global pointer value for MIPS, TOC address (address of .toc) for PowerPC, always 0 for ARM. |
ent_top | Offset to top of exports array |
ent_end | Offset to end of exports array |
stub_top | Offset to top of imports array |
stub_end | Offset to end of imports array |
dbg_fingerprint | It was wrongly named module NID. It is a sort of hash to ensure integrity and versioning. |
tls_top | Offset to top of the Thread Local Storage (TLS). |
tls_filesz | Certainly equals (tls_end - tls_top). |
tls_memsz | Certainly equals (tls_initialized_data_end - tls_top). |
start_entry | Offset to module_start function. -1 to disable. |
stop_entry | Offset to module_stop function. -1 to disable. |
arm_exidx_top | Offset to top of ARM EXIDX (optional) |
arm_exidx_end | Offset to end of ARM EXIDX (optional) |
arm_extab_top | Offset to top of ARM EXTAB (optional) |
arm_extab_end | Offset to end of ARM EXTAB (optional) |
#define MODULE_NAME_MAX_LEN 27 typedef struct SceModuleInfo_common { // size is 0x20 unsigned short modattribute; unsigned char modversion[2]; char modname[26]; // MODULE_NAME_MAX_LEN - 1 char terminal; // \0 char infoversion; } SceModuleInfo_common; typedef struct _scemoduleinfo { // size is 0x34 SceModuleInfo_common c; // c.infoversion must be 0 Elf32_Addr gp_value; Elf32_Addr ent_top; Elf32_Addr ent_end; Elf32_Addr stub_top; Elf32_Addr stub_end; } sceModuleInfo; typedef struct _scemoduleinfo1 { // size is 0x40 SceModuleInfo_common c; // c.infoversion must be 1 Elf32_Addr gp_value; Elf32_Addr ent_top; Elf32_Addr ent_end; Elf32_Addr stub_top; Elf32_Addr stub_end; Elf32_Word dbg_fingerprint; Elf32_Addr start_entry; Elf32_Addr stop_entry; } sceModuleInfo1; typedef struct _scemoduleinfo_arm { // size is 0x48 SceModuleInfo_common c; // c.infoversion must be 2 Elf32_Addr resreve; Elf32_Addr ent_top; Elf32_Addr ent_end; Elf32_Addr stub_top; Elf32_Addr stub_end; Elf32_Word dbg_fingerprint; Elf32_Addr start_entry; Elf32_Addr stop_entry; Elf32_Addr arm_exidx_top; Elf32_Addr arm_exidx_end; } sceModuleInfo_arm; typedef struct _scemoduleinfo_arm_tls { // size is 0x54 SceModuleInfo_common c; // c.infoversion must be 3 Elf32_Addr resreve; Elf32_Addr ent_top; Elf32_Addr ent_end; Elf32_Addr stub_top; Elf32_Addr stub_end; Elf32_Word dbg_fingerprint; Elf32_Addr start_entry; Elf32_Addr stop_entry; Elf32_Addr arm_exidx_top; Elf32_Addr arm_exidx_end; Elf32_Addr tls_top; Elf32_Addr tls_filesz; Elf32_Addr tls_memsz; } sceModuleInfo_arm_tls; // Note: There is no sceModuleInfo format for ARM that has version 4 or 5 so these versions may be // the sceModuleInfo_ppu32, sceModuleInfo_ppu64 or sceModuleInfo1_ppu32 formats. typedef struct _scemoduleinfo_prx2arm { // size is 0x5C SceModuleInfo_common c; // c.infoversion must be 6 Elf32_Addr resreve; Elf32_Addr ent_top; Elf32_Addr ent_end; Elf32_Addr stub_top; Elf32_Addr stub_end; Elf32_Word dbg_fingerprint; Elf32_Addr tls_top; Elf32_Addr tls_filesz; Elf32_Addr tls_memsz; Elf32_Addr start_entry; Elf32_Addr stop_entry; Elf32_Addr arm_exidx_top; Elf32_Addr arm_exidx_end; Elf32_Addr arm_extab_top; Elf32_Addr arm_extab_end; } sceModuleInfo_prx2arm;
Module attribute
The attributes of a module (PSP, PS Vita, ?PS3 to check?). Bitwise OR'ed values from ::SceModuleAttribute and ::SceModulePrivilegeLevel. Since PS Vita SDK 0.940, SceModulePrivilegeLevel are not set anymore and unused in PS Vita OS.
- Most usermode modules and a few kernel modules have it set to 0.
- Most kernel modules have it set to 7.
- Attribute 0x800 was seen in FW 0.902 cui_update_starter_module.self and FW 0.995 debug comicreader.elf. That attribute was set because these ELFs were compiled with an old PS Vita SDK (<= 0.930) that inherited privilege level attributes from the PSP SDK.
Value | Name | Comments |
---|---|---|
?0x20? | SCE_MODULE_ATTR_CANT_SHARE | |
?0x10? | SCE_MODULE_ATTR_CAN_RELOCATE | |
?0x8? | SCE_MODULE_ATTR_CAN_RESTART | |
0x4 | SCE_MODULE_ATTR_EXCLUSIVE_START | Only one instance of the module can be started. If you want to start another version of that module, you have to stop the currently running version first. |
0x2 | SCE_MODULE_ATTR_EXCLUSIVE_LOAD | Only one instance of the module can be loaded. If you want to load another version of that module, you have to unload the currently loaded version first. |
0x1 | SCE_MODULE_ATTR_CANT_STOP | Resident module i.e. module that stays in memory til poweroff. Such a module cannot be unloaded. |
0x0 | SCE_MODULE_ATTR_NONE | No module attributes. |
/** * Module type attributes. */ enum SceModuleAttribute { SCE_MODULE_ATTR_NONE = 0x0000, SCE_MODULE_ATTR_CANT_STOP = 0x0001, SCE_MODULE_ATTR_EXCLUSIVE_LOAD = 0x0002, SCE_MODULE_ATTR_EXCLUSIVE_START = 0x0004, }; /** * Module Privilege Levels - These levels define the permissions a * module can have. */ enum SceModulePrivilegeLevel { /** Lowest permission. */ SCE_MODULE_USER = 0x0000, /** MS modeul. POPS/Demo. */ SCE_MODULE_MS = 0x0200, /** USB WLAN module. Gamesharing */ SCE_MODULE_USBWLAN = 0x0400, /** Application module. */ SCE_MODULE_APP = 0x0600, /** VSH module. */ SCE_MODULE_VSH = 0x0800, /** Kernel module. Highest permission. */ SCE_MODULE_KERNEL = 0x1000, /** The module uses KIRK's memlmd resident library. */ SCE_MODULE_KIRK_MEMLMD_LIB = 0x2000, /** The module uses KIRK's semaphore resident library. */ SCE_MODULE_KIRK_SEMAPHORE_LIB = 0x4000 };
Modules imports-exports
Each module contains zero or more library exports and zero or more library imports. An exported library groups together related functions along with their NID and exports it for SceKernelModulemgr to link with a library import in another module.
Exports
An array of export entries defines all the libraries exported by the module.
Structure
Kernel modules that export syscalls (user accessible libraries) also get entries added to the syscall table. The syscall table is randomized on each boot so the same function will likely get a different syscall number assigned each time.
Name | Description |
---|---|
structsize | Size of the structure in bytes (usually 0x1C or 0x20) |
auxattribute | Unknown. Was added recently. "reserved1" in SDK 0.945, may be reserved since. |
version | Library version (usually 1) |
attribute | Library attributes bitflag |
nfunc | Number of exported functions |
nvar | Number of exported variables |
ntlsvar | Number of exported TLS variables |
hashinfo | Hash info (funcinfo + varinfo<<4). See Modules#Hash_Info. |
hashinfotls | TLS hash info. See Modules#Hash_Info. |
reserved2 | Reserved. |
nidaltsets | Unknown. Usually 0. |
libname | Pointer to library name. 0 for NONAME library. |
libname_nid | Library NID (=NID computed from library name) |
nidtable | Pointer to array of the exports' NIDs |
addtable | Pointer to array of the exports' addresses |
typedef struct _scelibent_psp { // size is 0x10 or 0x14 | Unofficial name? 0x14 form may be sceKernelLibraryEntryTable Elf32_Addr libname; /* <libname> (0 if NONAME) */ unsigned short version; /* <version> */ unsigned short attribute; /* <attribute> */ unsigned char size; /* struct size in dwords */ unsigned char nvar; /* number of variables */ unsigned short nfunc; /* number of functions */ Elf32_Addr entry_table; /* <entry_table> addr (in .rodata.sceResident) */ union { unsigned int unk_0x10; /* Not always present. Might be alias_table. ex: 0x20000 */ }; } SceLibEntryTable_psp; typedef struct _scelibent { //Not used on PSP2. Used for PSP when structsize == 0x14? Elf32_Addr libname; unsigned short version; unsigned short attribute; unsigned char structsize; unsigned char nvar; unsigned short nfunc; Elf32_Addr entrytable; unsigned short nvar2; unsigned char hashinfo; unsigned char nidaltsets; } sceKernelLibraryEntryTable; typedef struct _scelibent_ppu_common { unsigned char structsize; unsigned char reserved1[1]; //a.k.a. 'auxattribute' unsigned short version; unsigned short attribute; unsigned short nfunc; unsigned short nvar; unsigned short ntlsvar; unsigned char hashinfo; unsigned char hashinfotls; unsigned char reserved2[1]; unsigned char nidaltsets; } sceKernelLibraryEntryTable_ppu_common; typedef struct _scelibent_ppu32 { sceKernelLibraryEntryTable_ppu_common c; Elf32_Addr libname; Elf32_Addr nidtable; Elf32_Addr addtable; } sceKernelLibraryEntryTable_ppu32; //Old name: SceLibEntryTable_1C. Used in PRX1 format. typedef struct _scelibent_ppu32 sceKernelLibraryEntryTable_arm; typedef struct _scelibent_ppu_common sceKernelLibraryEntryTable_prx2_common; typedef struct _scelibent_prx2arm { sceKernelLibraryEntryTable_prx2_common c; Elf32_Word libname_nid; Elf32_Addr libname; Elf32_Addr nidtable; Elf32_Addr addtable; } sceKernelLibraryEntryTable_prx2arm; //Old name: SceLibEntryTable_20. Used in PRX2 format.
Hash Info
Hash info is a number which depends of the number of exports. It can have values: 0, 2, 4 or 6.
It is related to NID Hash Table, which is still a mystery.
#define HASHINFO_LIMIT 0x10 int getHashInfo(uint16_t num) { if ((HASHINFO_LIMIT == 0) || (num < HASHINFO_LIMIT)) return 0; else if (num < 0x40) return 2; else if (num < 0x100) return 4; else return 6; } int getHashInfo(SceLibEntryTable_common *table) { return getHashInfo(table->nfunc) + getHashInfo(table->nvar) << 4; } int getHashInfoTls(SceLibEntryTable_common *table) { return getHashInfo(table->ntls); }
NONAME exports
Old name was "syslib", short name for "system library".
There is a special export entry that always shows up (even when the module exports no libraries) with attribute 0x8000 and NID 0x00000000 that exports the module_start, module_stop, module_exit functions for example.
The NIDs for these exports are common for all modules. See here for NID generation algorithm.
For PS Vita:
Name | Type | NID | Definition |
---|---|---|---|
module_start | Function | 0x935CD196 | int module_start(SceSize arglen, const void *argp);
|
module_stop | Function | 0x79F8E492 | int module_stop(SceSize arglen, const void *argp);
|
module_exit | Function | 0x913482A9 | int module_exit(SceSize arglen, const void *argp);
|
module_bootstart | Function | 0x5C424D40 | int module_bootstart(SceSize arglen, const void *argp);
|
module_suspend | Function | 0xDD42FA37 | int module_suspend(void); Supported in FW 0.990.030, unsupported as of firmware 3.65. Present in FW 0.990.030 SceKernelModulemgr.
|
module_proc_create | Function | 0xE640E30C | SceInt32 module_proc_create(ScePID pid, void* pParam, void* pCommon); Supported in FW 0.931.010-0.990.030, unsupported as of firmware 3.65. Seemingly never used.
|
module_proc_exit | Function | 0x4F0EE5BD | SceInt32 module_proc_exit(ScePID pid, void* pParam, void* pCommon); Supported in FW 0.931.010-0.990.030, unsupported as of firmware 3.65. Seemingly never used.
|
module_proc_kill | Function | 0xDF0212B9 | SceInt32 module_proc_kill(ScePID pid, void* pParam, void* pCommon); Supported in FW 0.931.010-0.990.030, unsupported as of firmware 3.65. Seemingly never used.
|
module_info | Variable | 0x6C2224BA | SceModuleInfo structure
|
module_proc_param | Variable | 0x70FBA1E7 | SceProcessParam structure
|
module_sdk_version | Variable | 0x936C8A78 | SceUInt32 variable - Used since at least System Software version 2.10.
|
module_dtrace_probes_info | Variable | 0x9318D9DD | sdt_probes_info_t structure. Present in SceKernelThreadMgr, SceProcessmgr, SceDisplay. Reserved for kernel modules. Used by SceDeci4pDtracep.
|
module_dtrace_probes | Variable | 0x8CE938B1 | An array of sdt_probes_info_t.count pointers to sdt_probedesc_t . Present in SceKernelThreadMgr, SceProcessmgr, SceDisplay. Reserved for kernel modules. Used by SceDeci4pDtracep.
|
sce_module_start_thread_parameter | Variable | 0x1A9822A4 | Pointer to SceModuleThreadParameter . Defines the parameters used to create the thread that runs the module_start function. Reserved for usermode modules.
|
sce_module_stop_thread_parameter | Variable | 0xD20886EB | Pointer to SceModuleThreadParameter . Defines the parameters used to create the thread that runs the module_stop function. Reserved for usermode modules.
|
For PSP:
Name | Type | NID | Definition |
---|---|---|---|
module_start | Function | 0xD632ACDB | int module_start(int arglen, const void *argp);
|
module_stop | Function | 0xCEE8593C | int module_stop(int arglen, const void *argp);
|
module_bootstart | Function | 0xD3744BE0 | int module_bootstart(int arglen, const void *argp);
|
module_reboot_before | Function | 0x2F064FA6 | int module_reboot_before(void);
|
module_reboot_phase | Function | 0xADF12745 | int module_reboot_phase(void)
|
module_info | Variable | 0xF01D73A7 | SceModuleInfo
|
module_start_thread_parameter | Variable | 0x0F7C276C | SceModuleThreadParameter
|
module_stop_thread_parameter | Variable | 0xCF0CC697 | SceModuleThreadParameter
|
module_reboot_before_thread_parameter | Variable | 0xF4F4299D | SceModuleThreadParameter
|
module_sdk_version | Variable | 0x11B97506 | int
|
module_linked | Variable | 0x900DADE1 | |
module_unlinked | Variable | 0x592743D8 |
# Temporary name was: SceModuleEntryThread typedef struct SceModuleThreadParameter { // Size is 0x10 bytes on PSP, 0x14 on Vita /* The number of thread parameters. Must be 4 on Vita. */ SceUInt32 numParams; /* The initial priority of the entry thread. Default value is SCE_KERNEL_DEFAULT_PRIORITY. */ SceUInt32 initPriority; /* The stack size of the entry thread. Default value is SCE_KERNEL_THREAD_STACK_SIZE_DEFAULT_USER_MAIN (256KiB). */ SceSize stackSize; /* The attributes of the entry thread. Always ignored on Vita. */ SceUInt32 attr; /* The CPU affinity mask of the entry thread. Only present on Vita. Default value is SCE_KERNEL_THREAD_CPU_AFFINITY_MASK_DEFAULT. */ SceInt32 cpuAffinityMask; } SceModuleThreadParameter; // Temporary names were: SceModuleStaticProbesInfo, SceModuleDTraceProbesInfo typedef struct sdt_probes_info { // size is 0x8 bytes on FWs 0.931.010-3.740.011 unsigned int version; // Always 1 unsigned int count; // Number of probes. ex: 1, 5, 7, 16 } sdt_probes_info_t; typedef union { void (*handler0)(const struct sdt_probedesc *desc); void (*handler1)(const struct sdt_probedesc *desc, uintptr_t arg0); void (*handler2)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1); void (*handler3)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2); void (*handler4)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3); void (*handler5)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4); void (*handler6)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5); void (*handler7)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, uintptr_t arg6); void (*handler8)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, uintptr_t arg7); void (*handler9)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, uintptr_t arg7, uintptr_t arg8); void (*handler10)(const struct sdt_probedesc *desc, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, uintptr_t arg7, uintptr_t arg8, uintptr_t arg9); } sdpd_handler_t; // Temporary names were: SceModuleStaticProbe, SceModuleDTraceProbe typedef struct sdt_probedesc { // size is 0x28 on FWs 0.931.010-3.740.011 unsigned int *sdpd_id; // probe ID const char *sdpd_provider; // name of provider const char *sdpd_name; // name of probe void *sdpd_offset; // instrumentation point (address) sdpd_handler_t *sdpd_handler_fn; // probe handler_fn function (NULL if disabled) void *sdpd_private; // probe private data void (*sdpd_create_fn)(const struct sdt_probedesc *desc); // probe create helper function (NULL if unused) void (*sdpd_enable_fn)(const struct sdt_probedesc *desc); // probe enable helper function (NULL if unused) void (*sdpd_disable_fn)(const struct sdt_probedesc *desc); // probe disable helper function (NULL f unused) void (*sdpd_destroy_fn)(const struct sdt_probedesc *desc); // probe destroy helper function (NULL if unused) } sdt_probedesc_t; typedef struct SceLibcParam { // size is 0x1C on FW 0.930, 0x30 on FWs 1.80-1.81, 0x38 on FWs 2.00-3.740.011 SceSize size; // Size of this structure SceLibEntryTable_1C *_SceLibcParam_0001_ent_head; // Pointer to SceLibcParam library information SceSize *sceLibcHeapSize; // Pointer to heap size SceSize *__sceLibcHeapSizeDefault; // Pointer to heap size SceUInt32 *sceLibcHeapExtendedAlloc; // Pointer to flag to dynamically extend heap size SceUInt32 *sceLibcHeapDelayedAlloc; // Pointer to flag to allocate heap on first call to malloc SceUInt32 sdk_version; // SDK version SceUInt32 unk_1C; // ex: 9 void *__sce_libc_alloc_replace; // Pointer to replacement functions for Libc memory allocation functions void *__sce_libcxx_alloc_replace; // Pointer to replacement functions for Libcxx (C++) memory allocation functions SceSize *heap_initial_size; // Pointer to dynamically allocated heap initial size SceUInt32 *heap_unit_1mb; // Pointer to flag to change alloc unit size from 64kB to 1MB union { struct { SceUInt32 *heap_detect_overrun; // Pointer to flag to detect heap buffer overruns void *__sce_libc_tls_alloc_replace; // Pointer to replacement functions for TLS memory allocation functions }; }; } SceLibcParam; typedef struct SceProcessParam { // size is 0x20 on FW 0.895, 0x2C on FW 0.931.010, 0x30 on FW 0.945, 0x34 on FW 3.60 SceSize size; // Size of this structure SceUInt32 magic; // "PSP2" SceUInt32 version; // Version of this structure. Implies number of extended entries. ex: 1 on FW 0.895, 4 on FW 0.931.010, 5 on FW 0.945, 6 on FW 3.60 SceUInt32 sdk_version; // ex: 0x00895000 on FW 0.895.000, 0x00931010 on FW 0.931.010 union { struct { char *sceUserMainThreadName; // ex: "main_thread" SceInt32 sceUserMainThreadPriority; // ex: 0x20, 0xA0, 0x10000100 SceUInt32 sceUserMainThreadStackSize; // ex: 256 * 1024, 1024 * 1024 SceUInt32 sceUserMainThreadAttribute; char *sceProcessName; SceUInt32 sce_process_preload_disabled; SceUInt32 sceUserMainThreadCpuAffinityMask; SceLibcParam *sce_libcparam; SceUInt32 unk_0x30; }; }; } SceProcessParam;
Imports
Each module can import any number of libraries from other modules by specifying the library NID to import along with a list of functions and variables NIDs to import. SceKernelModulemgr does the dynamic linking by overwriting the import thunk (with an absolute address jump if both modules are in the same domain (usermode or kernel), or a syscall sequence for a usermode module importing a kernel-exported library). Kernel modules cannot import usermode libraries.
Name | Description |
---|---|
structsize | Size of the structure (usually 0x24, 0x2C or 0x34) |
version | Library version (ex: 1, 5) |
attribute | Library attributes bitflag |
nfunc | Number of imported functions |
nvar | Number of imported variables |
ntlsvar | Number of imported TLS variables |
reserved | Reserved |
libname_nid | Library NID |
libname | Pointer to library name |
sce_sdk_version | Usually 0 |
func_nidtable | Pointer to array of imported functions NIDs |
func_table | Pointer to array of imported functions thunks |
var_nidtable | Pointer to array of imported variables NIDs |
var_table | Pointer to array of pointers to imported variables' reftables |
tls_nidtable | Pointer to array of imported TLS variables NIDs |
tls_table | Pointer to array of pointers to imported TLS variables' reftables |
typedef struct _scelibstub_psp { // size is 0x14 or 0x18 Elf32_Addr libname; /* library name */ unsigned short version; /* version */ unsigned short attribute; /* attribute */ unsigned char size; /* struct size */ unsigned char nvar; /* number of variables */ unsigned short nfunc; /* number of function stubs */ Elf32_Addr nid_table; /* functions/variables NID table */ Elf32_Addr stub_table; /* functions stub table */ union { Elf32_Addr var_table; /* variables table */ }; } SceLibStubTable_psp; typedef struct _scelibstub { // size is 0x1C. Not used on PSP2. Maybe used on PSP. Elf32_Addr libname; unsigned short version; unsigned short attribute; unsigned char structsize; unsigned char nvar; unsigned short nfunc; Elf32_Addr nidtable; Elf32_Addr stubtable; Elf32_Addr vstubtable; unsigned short nvar2; unsigned char reverse1; unsigned char reverse2; } sceKernelLibraryStubTable; typedef struct _scelibstub_ppu_common { unsigned char structsize; unsigned char reserved1[1]; unsigned short version; unsigned short attribute; unsigned short nfunc; unsigned short nvar; unsigned short ntlsvar; unsigned char reserved2[4]; } sceKernelLibraryStubTable_ppu_common; typedef struct _scelibstub_ppu32 { sceKernelLibraryStubTable_ppu_common c; Elf32_Addr libname; Elf32_Addr func_nidtable; Elf32_Addr func_table; Elf32_Addr var_nidtable; Elf32_Addr var_table; Elf32_Addr tls_nidtable; Elf32_Addr tls_table; } sceKernelLibraryStubTable_ppu32; typedef struct _scelibstub_ppu32 sceKernelLibraryStubTable_arm; // Old name: SceLibStubTable_2C. Used in PRX1 format. typedef struct _scelibstub_ppu_common sceKernelLibraryStubTable_prx2_common; typedef struct _scelibstub_prx2arm { // size is 0x34 - Used in PRX2 format. sceKernelLibraryStubTable_prx2_common c; Elf32_Word libname_nid; Elf32_Addr libname; Elf32_Word sce_sdk_version; Elf32_Addr func_nidtable; Elf32_Addr func_table; Elf32_Addr var_nidtable; Elf32_Addr var_table; Elf32_Addr tls_nidtable; Elf32_Addr tls_table; } sceKernelLibraryStubTable_prx2arm; // Old name: SceLibStubTable_34 typedef struct _scelibstub_prx2arm_new { // size is 0x24 - Used in PRX2 format. This is a guessed name. unsigned char structsize; unsigned char reserved1[1]; unsigned short version; unsigned short attribute; unsigned short nfunc; unsigned short nvar; unsigned short ntlsvar; Elf32_Word libname_nid; Elf32_Addr libname; Elf32_Addr func_nidtable; Elf32_Addr func_table; Elf32_Addr var_nidtable; // Variable + TLS variable NIDs (?) Elf32_Addr var_table; // Variable + TLS variable entries (?) } sceKernelLibraryStubTable_prx2arm_new; // Old name: SceLibStubTable_24
reftable
(Variable/function-address imports)
Variable imports are handled differently from function imports. While the entry in the function import table points to an import thunk, the entry in the variable import table points to a so-called reftable
(probably reference table). If an imported function is used for other purposes than a function call, a reftable
can also be generated for this function.
For example, the following C code verifies if a loosely imported function has been successfully bound to before calling it:
//NOTE: this may work with non-loosely imported functions but is non-sensical: //Modulemgr will never start a module if non-loose imports can't be bound, //so the check will always evaluate to true for such functions. //(The exception is app modules (e.g. eboot.bin) since ALL their imports are considered loose) if (&loose_import_func != NULL) { loose_import_func(); }
There are two reftable
formats corresponding to the two executable formats supported by the OS.
Prx1 format
Prx1 is the old format of PS vita executables. These executables use either the standard ELF types ET_EXEC
and ET_REL
, or the legacy 0xFFA5 (ET_SCE_ARMRELEXEC
) as the value in the ELF header's e_type
.
In this format, the reftable
is simply an array of Prx1RefInfo
s terminated by a NULL entry.
//8 bytes typedef struct Prx1RefInfo { //N.B. bit fields are in little-endian order //For example, reltype occupies bits 7-0 of the SceUInt32 SceUInt32 reltype:8; //Relocation type (r_type) SceUInt32 addend:24; //Relocation addend (r_addend) - sign-extended to 32-bits void* P; //Virtual address where the reference lies } Prx1RefInfo; typedef struct ScePrx1RefTable { Prx1RefInfo info[]; } ScePrx1RefTable; //Sample code to walk Prx1 reftable void walk_Prx1_reftable(ScePrx1RefTable* pTable) { unsigned* infoptr = (unsigned*)&reftable->info; while (*infoptr != 0) { Prx1RefInfo* info = (Prx1RefInfo*)infoptr; //...do something with info... infoptr += sizeof(Prx1RefInfo)/sizeof(unsigned); } }
Prx2 format
Prx2 is the new format of PS Vita executables, in use since around PS Vita SDK 0.940. These executables use 0xFE00 (ET_SCE_EXEC
) or 0xFE04 (ET_SCE_RELEXEC
) as e_type
.
In this format, a 32-bit value is prepended to the table and there are two possible forms for refinfo
instead of a single one.
There are two revisions of the reftable
that change the meaning of this 32-bit value (note that the size
includes the 32-bit value in it):
Bit mask | Header text |
---|---|
New firmwares | |
0xF0000000 | Must be 0 |
0x0FFFFFF0 | reftable size (in bytes)
|
0x0000000F | Reftable version? Must be 0 |
Old firmwares | |
0xFFFF0000 | Unused |
0x0000FFF0 | reftable size (in bytes)
|
0x0000000F | Reftable version? Must be 0 |
//8 bytes typedef struct Prx2RefInfoForm1 { SceUInt32 form:4; //1 SceUInt32 segment:4; //Segment where reference lies SceUInt32 reltype:8; //Relocation type (r_type) SceUInt32 addend:16; //Relocation addend (r_addend) - sign-extended to 32-bits SceUInt32 offset; //Offset where reference lies in segment (r_offset) } Prx2RefInfoForm1; //12 bytes typedef struct Prx2RefInfoForm2 { SceUInt32 form:4; //2 SceUInt32 segment:4; //Segment where reference lies SceUInt32 reltype:8; //Relocation type (r_type) SceUInt32 unused:16; //Unused (padding) SceInt32 addend; //Relocation addend (r_addend) SceUInt32 offset; //Offset where reference lies in segment (r_offset) } Prx2RefInfoForm2; typedef struct ScePrx2RefTable_old { //Used until firmware ?.?? SceUInt32 version:4; //Must be 0 SceUInt32 size:12; SceUInt32 unused:16; unsigned char info[/* size - sizeof(SceUInt32) */]; } ScePrx2RefTable_old; typedef struct ScePrx2RefTable { //Used since firmware ?.?? SceUInt32 version:4; //Must be 0 SceUInt32 size:24; SceUInt32 unk0_28:4; //Must be 0 unsigned char info[/* size - sizeof(SceUInt32) */]; } ScePrx2RefTable; //Sample code to walk Prx2 reftable void walk_Prx2_reftable(ScePrx2RefTable* pTable) { /*assert(pTable->unk0_28 == 0); //for new format only */ assert(pTable->version == 0); unsigned size = pTable->size; unsigned* infoptr = (unsigned*)pTable->info; unsigned* maxptr = (unsigned*)(pTable->info + size); while(infoptr < maxptr) { unsigned form = *infoptr & 0xF; unsigned infosize; switch(form) { case 1: infosize = sizeof(Prx2RefInfoForm1); Prx2RefInfoForm1* info = (Prx2RefInfoForm1*)infoptr; //...do something with info... break; case 2: infosize = sizeof(Prx2RefInfoForm2); Prx2RefInfoForm2* info = (Prx2RefInfoForm2*)infoptr; //...do something with info... break; default: assert(0); } infoptr += size/sizeof(unsigned); } assert(infoptr == maxptr); }
Function reftable
s
To obtain the pointer to a function's associated reftable
, use the following code:
void* get_function_reftable(SceLibStubTable_xxx* libstub, int function_index) { uintptr_t import_thunk = (uintptr_t)libstub->func_entry_table[function_index]; //Import thunks are made of 3 ARM instructions (4 bytes each) //and a 4-byte pointer to the reftable (may be NULL) return *(void**)(import_thunk + 3 * 4); }
Library attribute
Library attribute flags are ORed together and identified as follows:
Bit | Name | Comments |
---|---|---|
0x8000 | ?MAIN_EXPORT? | Set for main NONAME export. |
0x4000 | SYSCALL_EXPORT | In kernel modules only. Allow syscall export to usermode. |
0x2000 | PLUGIN_LINK | Lets the library's exports table be managed on-the-fly by the module itself. If used together with --plugin-header option, switches armlibgen.exe to some special mode, making it generate headers for use with SceLibKernel#sceKernelLinkFunctionTable. It also requires function prototypes to be defined in the EMD file. On PS3, it seems to indicate a non-PRX library (like "stdc" or "allocator") that comes from somewhere else (LV2?). |
0x8 | LOOSE_IMPORT | ?Set when the library has the WEAK_EXPORT flag in its exporting module.? |
0x4 | NOLINK_EXPORT | |
0x2 | WEAK_EXPORT | ?kernel non-driver export? |
0x1 | AUTO_EXPORT | Makes the library importable. Should be set unless it is the main export. |
Relocations
See [1] for more information on how PS Vita relocations work.
Relocations are stored within the PT_SCE_RELA segment.
There are 10 relocation entry formats. It is determined by r_format (the first 4 bits of the relocation entry).
Warning! In PS Vita FWs <= 3.00, only relocation entry format 0 is available. Using any other relocation entry format gives error 0x8002D019.
// assuming LSB of bitfield is listed first union { Elf32Word r_format: 4; struct { Elf32Word r_format : 4; Elf32Word r_symbol_segment : 4; Elf32Word r_type : 8; Elf32Word r_patch_segment : 4; Elf32Word r_type2 : 8; Elf32Word r_dist2 : 4; Elf32Word r_addend; Elf32Word r_offset; } r_entry_0; struct { Elf32Word r_format : 4; Elf32Word r_symbol_segment : 4; Elf32Word r_type : 8; Elf32Word r_patch_segment : 4; Elf32Word r_offset_lo : 12; Elf32Word r_offset_hi : 10; Elf32Word r_addend : 22; } r_entry_1; // Used by var import relocations struct { Elf32Word r_format : 4; Elf32Word r_symbol_segment : 4; Elf32Word r_type : 8; Elf32Word r_patch_segment : 2; Elf32Word r_unk : 2; Elf32Word r_pad : 4; Elf32Word r_offset; } r_entry_1_alt; struct { Elf32Word r_format : 4; Elf32Word r_symbol_segment : 4; Elf32Word r_type : 8; Elf32Word r_offset : 16; Elf32Word r_addend; } r_entry_2; struct { Elf32Word r_format : 4; Elf32Word r_symbol_segment : 4; Elf32Word r_ins_mode : 1; // ARM = 0, THUMB = 1 Elf32Word r_offset : 18; Elf32Word r_dist2 : 5; Elf32Word r_addend : 22; } r_entry_3; struct { Elf32Word r_format : 4; Elf32Word r_offset : 23; Elf32Word r_dist2 : 5; } r_entry_4; struct { Elf32Word r_format : 4; Elf32Word r_dist1 : 9; Elf32Word r_dist2 : 5; Elf32Word r_dist3 : 9; Elf32Word r_dist4 : 5; } r_entry_5; struct { Elf32Word r_format : 4; Elf32Word r_offset : 28; } r_entry_6; struct { Elf32Word r_format : 4; // r_format 7 has 4 offsets, 7 bits each // r_format 8 has 7 offsets, 4 bits each // r_format 9 has 14 offsets, 2 bits each Elf32Word offsets : 28; } r_entry_7_8_9; } SceRel;
Format 0
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 0. |
4 | 7 | Symbol segment index |
8 | 15 | Relocation type |
16 | 19 | Patch segment index |
20 | 26 | Second relocation type |
27 | 31 | Distance from offset (used for second relocation) |
32 | 63 | Addend |
64 | 95 | Offset |
Format 1
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 1. |
4 | 7 | Symbol segment index |
8 | 15 | Relocation type |
16 | 19 | Patch segment index |
20 | 41 | Offset |
42 | 63 | Addend |
Format 2
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 2. |
4 | 7 | Symbol segment index |
8 | 15 | Relocation type |
16 | 31 | Offset |
32 | 64 | Addend |
Format 3
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 3 |
4 | 7 | Symbol segment index |
8 | 8 | Mode (ARM = 0, THUMB = 1) |
9 | 26 | Offset |
27 | 31 | Distance from offset |
32 | 64 | Addend |
Format 4
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 4. |
4 | 26 | Offset |
27 | 31 | Distance from offset |
Format 5
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 5. |
4 | 12 | Distance 1 |
13 | 17 | Distance 2 |
18 | 26 | Distance 3 |
27 | 31 | Distance 4 |
Format 6
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 6. |
4 | 31 | Offset |
Format 7
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 7. |
4 | 10 | Offset 1 |
11 | 17 | Offset 2 |
18 | 24 | Offset 3 |
25 | 31 | Offset 4 |
Format 8
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 8. |
4 | 7 | Offset 1 |
8 | 11 | Offset 2 |
12 | 15 | Offset 3 |
16 | 19 | Offset 4 |
20 | 23 | Offset 5 |
24 | 27 | Offset 6 |
28 | 31 | Offset 7 |
Format 9
Start | End | Description |
---|---|---|
0 | 3 | Entry format. Set to 9. |
4 | 5 | Offset 1 |
6 | 7 | Offset 2 |
8 | 9 | Offset 3 |
10 | 11 | Offset 4 |
12 | 13 | Offset 5 |
14 | 15 | Offset 6 |
16 | 17 | Offset 7 |
18 | 19 | Offset 8 |
20 | 21 | Offset 9 |
22 | 23 | Offset 10 |
24 | 25 | Offset 11 |
26 | 27 | Offset 12 |
28 | 29 | Offset 13 |
30 | 31 | Offset 14 |
Supported Relocation Codes (as of FW 3.60)
Code | Name | Description |
---|---|---|
0 | R_ARM_NONE | No relocation |
2 | R_ARM_ABS32 | Direct 32-bits address |
3 | R_ARM_REL32 | N/A |
10 | R_ARM_THM_CALL | bl/blx on Thumb mode |
28 | R_ARM_CALL | bl/blx on ARM mode |
29 | R_ARM_JUMP24 | N/A |
38 | R_ARM_TARGET1 | same as R_ARM_ABS32 |
40 | R_ARM_V4BX | same as R_ARM_NONE |
41 | R_ARM_TARGET2 | same as R_ARM_REL32 |
42 | R_ARM_PREL31 | N/A |
43 | R_ARM_MOVW_ABS_NC | movw on ARM mode |
44 | R_ARM_MOVT_ABS | movt on ARM mode |
47 | R_ARM_THM_MOVW_ABS_NC | movw on Thumb mode |
48 | R_ARM_THM_MOVT_ABS | movt on Thumb mode |
255 | R_ARM_RBASE | Ignored |
Tools
Tools for IDA PRO
Tools for Ghidra
- PS Vita PRX Loader for Ghidra by xerpi
- PS Vita PRX Loader for Ghidra by CreepNT
- PS Vita/PSP PRX Loader for Ghidra by CelesteBlue (link to add)