El PRX, ese gran desconocido

Moderadores: Kravenbcn, largeroliker, fidelcastro, cerealkiller, pspCaracas, dark_sasuke, m0skit0, LnD, ka69, zacky06

Responder
Avatar de Usuario
m0skit0
Administrador
Administrador
Mensajes: 5585
Registrado: 03 Sep 2009, 09:35
Ubicación: 0xdeadbeef

El PRX, ese gran desconocido

Mensaje por m0skit0 »

[youtube]http://www.youtube.com/watch?v=dqidL_uaQ-k[/youtube]


Un PRX (PlayStation Relocatable Executable) es un fichero ELF ejecutable o librería de PSP (también usado en otras consolas de Sony con una estructura similar). Un ELF es un formato estándar de los sistemas UNIX que generalmente usan este tipo de formato para sus ejecutables (Linux, BSD, Mac OS X...).

Este formato contiene obviamente código (en binario, es decir, código máquina) pero también información adicional para el sistema operativo de cómo se debe cargar este código en memoria, dónde están y cómo resolver las llamadas al sistema (syscalls) y cómo relocalizarlo para que funcione. Relocalizar (Relocate) significa que este tipo de ejecutables/librerías se pueden cargar en cualquier dirección de memoria. Esto implica tener que modificar partes del ejecutable para que se ejecuten correctamente. Por ejemplo la instrucción MIPS J (Jump) necesita una dirección de memoria absoluta, por tanto hay que modificar esta intrucción dependiendo de dónde carguemos el ejecutable.

Un ELF se divide en secciones, donde cada sección almacena un tipo de información diferente sobre el ejecutable (incluído el código). Algunas secciones se cargan en memoria y otras no son necesarias.

Podéis ver la estructura básica de un PRX en C en elf.h del HBL.

Dicho esto, vamos a echarle un vistazo al desensamblado del PRX:

Código: Seleccionar todo

2 ; ==== Section .text - Address 0x00000000 Size 0x0000AD38 Flags 0x0006

Ésta es la sección .text, que hace referencia al código. Address 0x00000000 significa que esta sección se debe cargar en la dirección indicada 0x00000000. Recordemos que este es un PRX, por tanto relocalizable, con lo que 0x00000000 realmente no será la dirección dónde se cargará esta sección, sino el desplazamiento (offset) respecto a la dirección base dónde cargará el SO esta sección. Por ejemplo si el SO carga este ejecutable en la dirección 0x08000000, entonces esta sección estará en 0x08000000 (dirección base) + 0x00000000 (desplazamiento sección) = 0x08000000 (dirección absoluta). Todo lo que venga a continuación será .text hasta que se nos indique lo contrario.

Código: Seleccionar todo

6 sub_00000000:      ; Refs: 0x00000148 0x000000D0 0x00000204 0x00000264 

La subrutina sub_00000000. Normalmente esto corresponde a una función en C. El nombre original de la función realmente no se guarda al compilarse el código, por tanto prxtool se inventa un nombre: sub_dirección. Refs nos indica desde qué otros desplazamientos se llama a esta subrutina.

Código: Seleccionar todo

7   0x00000000: 0x27BDFF90 '...'' - addiu      $sp, $sp, -112

Instrucción (código). 0x00000000 es la dirección de memoria (vuelvo a recordar que esto realmente es un desplazamiento, no una dirección absoluta). 0x27BDFF90 es la instrucción en binario, en este caso código máquina MIPS; es el valor que realmente está en el fichero. '...'' es la representación en ASCII de la instrucción, donde . es un carácter ASCII no imprimible. addiu $sp, $sp, -112 es el ensamblador correspondiente a la instrucción en binario.

Código: Seleccionar todo

62 ; Subroutine module_start - Address 0x000000AC 
63 ; Exported in syslib

Esta subrutina sí tiene nombre porque está exportada como se indica en la línea 63 junto a la librería donde está incluída syslib; es module_start. Ésta es la subrutina de entrada del PRX, donde se empieza a ejecutar este ejecutable. Una subrutina está exportada cuando otros módulos están autorizados a accederla, es decir, otro código puede acceder a esta función para ejecutarla. En este caso es el kernel el que lanzará un hilo en modo usuario para lanzar este ejecutable.

Código: Seleccionar todo

14196 ; ==== Section .sceStub.text - Address 0x0000AD78 Size 0x000000B0 Flags 0x0006

La sección de los stubs. Vemos que también es text, por tanto también es código. Aquí están los importes de este ejecutable, es decir funciones incluídas en otro PRX del que depende este ejecutable, por ejemplo un PRX del firmware de PSP. Un ejemplo de función que se encontraría aquí sería sceIoOpen. Esta sección está referenciada en tStubEntry.jump_pointer, que veremos más adelante.

Código: Seleccionar todo

14199 ; Subroutine sceDisplay_0E20F177 - Address 0x0000AD78 
14200 ; Imported from sceDisplay

Un importe, en este caso la llamada 0E20F177 de la librería sceDisplay. sceDisplay es una librería que está en el firmware de PSP (lo sé porque soy muy listo). En PSP los importes se reconocen por un identificador denominado NID, un número de 32 bits sin signo que es totalmente único para todos los importes. En el caso de que el importe sea de una librería del firmware, se puede saber a qué función del SDK corresponde en grandes recopilaciones hechas por grandes sceners como Silverspring (en este caso la del firmware 5.00):

Código: Seleccionar todo

0x0E20F177   sceDisplaySetMode


Ahora si os fijáis, el código de todos los stubs es

Código: Seleccionar todo

jr $ra
nop

Esto cualquiera con un conocimiento decente de MIPS verá que hace exactamente... nada. Este código sólo retorna al código que llamó a esta subrutina. Esto es así porque al compilar este PRX, estos importes no se sabe dónde están porque están o bien en otros PRXs o bien en el firmware de la PSP. Por tanto el kernel del fimware (o también el HBL, que básicamente es un kernel en modo usuario) se encargará de resolver los stubs a la hora de ejecutar el PRX. Esto significa que el kernel tiene que saber a qué importe se refiere cada stub (esto veremos que está definido en una sección aparte del PRX más adelante) y conocer la librería y el NID a la que se refiere el importe, en este caso sceDisplay y 0E20F177 respectivamente. El kernel entonces procederá a sustituir el stub con

Código: Seleccionar todo

jr $ra
syscall <número de syscall>

en caso de una llamada a una función kernel, siendo <número de syscall> normalmente un valor como 0x2A98 o

Código: Seleccionar todo

j <dirección de memoria>
nop

en caso de una función modo usuario, siendo <dirección de memoria> una dirección de memoria de usuario (normalmente en el rango 0x08000000 - 0x09FFFFFF).

Código: Seleccionar todo

14372 ; ==== Section .lib.stub - Address 0x0000AE44 Size 0x000000DC Flags 0x0002

La sección .lib.stub informa sobre dónde están los stubs arriba mencionados, la librería asociada y el NID de cada stub. La estructura es la siguiente (extraído del elf.h):

Código: Seleccionar todo

/*******************/
/* .lib.stub entry */
/*******************/
typedef struct
{
        Elf32_Addr library_name;     // Pointer to library name
        Elf32_Half import_flags;
        Elf32_Half library_version;
        Elf32_Half import_stubs;
        Elf32_Half stub_size;        // Number of stubs imported from library
        Elf32_Addr nid_pointer;      // Pointer to array of NIDs from library
        Elf32_Addr jump_pointer;     // Pointer to array of stubs from library
} tStubEntry;

Creo que los comentarios explican bastante bien qué sirve cada miembro, pero si tenéis alguna pregunta os la aclaro encantado.

Código: Seleccionar todo

14406 ; ==== Section .rodata.sceResident - Address 0x0000AF70 Size 0x000000EC Flags 0x0002

Esta sección contiene básicamente los nombres de las librerías importadas, referenciados en tStubEntry.library_name.

Código: Seleccionar todo

14438 ; ==== Section .rodata.sceNid - Address 0x0000B05C Size 0x00000058 Flags 0x0002

Los NIDs importados. Si os fijáis la primera palabra (word) es precisamente 77 F1 20 0E, que es 0E20F177, el NID del primer stub que vimos antes. Y así todos los otros NIDs. Esta sección está referenciada en tStubEntry.nid_pointer.

Código: Seleccionar todo

14448 ; ==== Section .rodata - Address 0x0000B0B8 Size 0x00000520 Flags 0x0002

Datos de sólo lectura del PRX.

Código: Seleccionar todo

14572 ; ==== Section .data - Address 0x0000B728 Size 0x00001060 Flags 0x0003

Espacio para las variables globales del PRX, memoria pre-reservada de lectura/escritura.

[youtube]http://www.youtube.com/watch?v=s71XIKKG0n4[/youtube]


Un PRX "Hello world" desensamblado, cortesía de Darthvader38
No tiene los permisos requeridos para ver los archivos adjuntos a este mensaje.
Imagen

Avatar de Usuario
jjblanco93
Experto
Experto
Mensajes: 1090
Registrado: 11 Abr 2010, 18:20
PSN ID: jjblanco93
Gamertag Xbox Live: jjblanco1993
Steam ID: jjblanco93
Twitter: jjblanco1993
Ubicación: Gran Canaria-Islas Canarias

Re: El PRX, ese gran desconocido

Mensaje por jjblanco93 »

Muy bueno el aporte :oki:
Imagen

Spoiler:
Imagen

Avatar de Usuario
Darthvader38
Enteradillo
Enteradillo
Mensajes: 67
Registrado: 24 Ene 2010, 06:39

Re: El PRX, ese gran desconocido

Mensaje por Darthvader38 »

Probablemente este deberia haber sido uno de los primeros temas
del foro ->desarrollo...
Big aporte! ;)
Ahora lo estudio :geek:

Salu2 chavales!

Responder